Skip to content

Commit 1db5c8b

Browse files
Nicolas Pitrecarlescufi
Nicolas Pitre
authored andcommitted
scripts: gen_syscalls: fix argument marshalling with 64-bit debug builds
Let's consider this (simplified) compilation result of a debug build using -O0 for riscv64: |__pinned_func |static inline int k_sem_init(struct k_sem * sem, | unsigned int initial_count, | unsigned int limit) |{ | 80000ad0: 6105 addi sp,sp,32 | 80000ad2: ec06 sd ra,24(sp) | 80000ad4: e42a sd a0,8(sp) | 80000ad6: c22e sw a1,4(sp) | 80000ad8: c032 sw a2,0(sp) | ret = arch_is_user_context(); | 80000ada: b39ff0ef jal ra,80000612 | if (z_syscall_trap()) { | 80000ade: c911 beqz a0,80000af2 | return (int) arch_syscall_invoke3(*(uintptr_t *)&sem, | *(uintptr_t *)&initial_count, | *(uintptr_t *)&limit, | K_SYSCALL_K_SEM_INIT); | 80000ae0: 6522 ld a0,8(sp) | 80000ae2: 00413583 ld a1,4(sp) | 80000ae6: 6602 ld a2,0(sp) | 80000ae8: 0b700693 li a3,183 | [...] We clearly see the 32-bit values `initial_count` (a1) and `limit` (a2) being stored in memory with the `sw` (store word) instruction. Then, according to the source code, the address of those values is casted as a pointer to uintptr_t values, and that pointer is dereferenced to get back those values with the `ld` (load double) instruction this time. In other words, the assembly does exactly what the C code indicates. This is wrong for 2 reasons: - The top half of a1 and a2 will contain garbage due to the `ld` used to retrieve them. Whether or not the top bits will be cleared eventually depends on the architecture and compiler. - Regardless of the above, a1 and a2 would be plain wrong on a big endian system. - The load of a1 will cause a misaligned trap as it is 4-byte aligned while `ld` expects a 8-byte alignment. The above code happens to work properly when compiling with optimizations enabled as the compiler simplifies the cast and dereference away, and register content is used as is in that case. That doesn't make the code any more "correct" though. The reason for taking the address of an argument and dereference it as an uintptr_t pointer is most likely done to work around the fact that the compiler refuses to cast an aggregate value to an integer, even if that aggregate value is in fact a simple structure wrapping an integer. So let's fix this code by: - Removing the pointer dereference roundtrip and associated casts. This gets rid of all the issues listed above. - Using a union to perform the type transition which deals with aggregates perfectly well. The compiler does optimize things to the same assembly output in the end. This also makes the compiler happier as those pragmas to shut up warnings are no longer needed. It should be the same about coverity. Signed-off-by: Nicolas Pitre <[email protected]>
1 parent df80c77 commit 1db5c8b

File tree

1 file changed

+56
-84
lines changed

1 file changed

+56
-84
lines changed

scripts/gen_syscalls.py

+56-84
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@
6161
};
6262
"""
6363

64-
list_template = """
65-
/* auto-generated by gen_syscalls.py, don't edit */
64+
list_template = """/* auto-generated by gen_syscalls.py, don't edit */
65+
6666
#ifndef ZEPHYR_SYSCALL_LIST_H
6767
#define ZEPHYR_SYSCALL_LIST_H
6868
@@ -77,8 +77,8 @@
7777
#endif /* ZEPHYR_SYSCALL_LIST_H */
7878
"""
7979

80-
syscall_template = """
81-
/* auto-generated by gen_syscalls.py, don't edit */
80+
syscall_template = """/* auto-generated by gen_syscalls.py, don't edit */
81+
8282
{include_guard}
8383
8484
{tracing_include}
@@ -91,17 +91,6 @@
9191
#include <linker/sections.h>
9292
9393
94-
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
95-
#pragma GCC diagnostic push
96-
#endif
97-
98-
#ifdef __GNUC__
99-
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
100-
#if !defined(__XCC__)
101-
#pragma GCC diagnostic ignored "-Warray-bounds"
102-
#endif
103-
#endif
104-
10594
#ifdef __cplusplus
10695
extern "C" {{
10796
#endif
@@ -112,10 +101,6 @@
112101
}}
113102
#endif
114103
115-
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
116-
#pragma GCC diagnostic pop
117-
#endif
118-
119104
#endif
120105
#endif /* include guard */
121106
"""
@@ -196,25 +181,13 @@ def need_split(argtype):
196181
# Note: "lo" and "hi" are named in little endian conventions,
197182
# but it doesn't matter as long as they are consistently
198183
# generated.
199-
def union_decl(type):
200-
return "union { struct { uintptr_t lo, hi; } split; %s val; }" % type
184+
def union_decl(type, split):
185+
middle = "struct { uintptr_t lo, hi; } split" if split else "uintptr_t x"
186+
return "union { %s; %s val; }" % (middle, type)
201187

202188
def wrapper_defs(func_name, func_type, args, fn):
203189
ret64 = need_split(func_type)
204190
mrsh_args = [] # List of rvalue expressions for the marshalled invocation
205-
split_args = []
206-
nsplit = 0
207-
for argtype, argname in args:
208-
if need_split(argtype):
209-
split_args.append((argtype, argname))
210-
mrsh_args.append("parm%d.split.lo" % nsplit)
211-
mrsh_args.append("parm%d.split.hi" % nsplit)
212-
nsplit += 1
213-
else:
214-
mrsh_args.append("*(uintptr_t *)&" + argname)
215-
216-
if ret64:
217-
mrsh_args.append("(uintptr_t)&ret64")
218191

219192
decl_arglist = ", ".join([" ".join(argrec) for argrec in args]) or "void"
220193
syscall_id = "K_SYSCALL_" + func_name.upper()
@@ -228,10 +201,24 @@ def wrapper_defs(func_name, func_type, args, fn):
228201
wrap += ("\t" + "uint64_t ret64;\n") if ret64 else ""
229202
wrap += "\t" + "if (z_syscall_trap()) {\n"
230203

231-
for parmnum, rec in enumerate(split_args):
232-
(argtype, argname) = rec
233-
wrap += "\t\t%s parm%d;\n" % (union_decl(argtype), parmnum)
234-
wrap += "\t\t" + "parm%d.val = %s;\n" % (parmnum, argname)
204+
valist_args = []
205+
for argnum, (argtype, argname) in enumerate(args):
206+
split = need_split(argtype)
207+
wrap += "\t\t%s parm%d" % (union_decl(argtype, split), argnum)
208+
if argtype != "va_list":
209+
wrap += " = { .val = %s };\n" % argname
210+
else:
211+
# va_list objects are ... peculiar.
212+
wrap += ";\n" + "\t\t" + "va_copy(parm%d.val, %s);\n" % (argnum, argname)
213+
valist_args.append("parm%d.val" % argnum)
214+
if split:
215+
mrsh_args.append("parm%d.split.lo" % argnum)
216+
mrsh_args.append("parm%d.split.hi" % argnum)
217+
else:
218+
mrsh_args.append("parm%d.x" % argnum)
219+
220+
if ret64:
221+
mrsh_args.append("(uintptr_t)&ret64")
235222

236223
if len(mrsh_args) > 6:
237224
wrap += "\t\t" + "uintptr_t more[] = {\n"
@@ -243,21 +230,23 @@ def wrapper_defs(func_name, func_type, args, fn):
243230
% (len(mrsh_args),
244231
", ".join(mrsh_args + [syscall_id])))
245232

246-
# Coverity does not understand syscall mechanism
247-
# and will already complain when any function argument
248-
# is not of exact size as uintptr_t. So tell Coverity
249-
# to ignore this particular rule here.
250-
wrap += "\t\t/* coverity[OVERRUN] */\n"
251-
252233
if ret64:
253-
wrap += "\t\t" + "(void)%s;\n" % invoke
254-
wrap += "\t\t" + "return (%s)ret64;\n" % func_type
234+
invoke = "\t\t" + "(void) %s;\n" % invoke
235+
retcode = "\t\t" + "return (%s) ret64;\n" % func_type
255236
elif func_type == "void":
256-
wrap += "\t\t" + "%s;\n" % invoke
257-
wrap += "\t\t" + "return;\n"
237+
invoke = "\t\t" + "(void) %s;\n" % invoke
238+
retcode = "\t\t" + "return;\n"
239+
elif valist_args:
240+
invoke = "\t\t" + "%s retval = %s;\n" % (func_type, invoke)
241+
retcode = "\t\t" + "return retval;\n"
258242
else:
259-
wrap += "\t\t" + "return (%s) %s;\n" % (func_type, invoke)
243+
invoke = "\t\t" + "return (%s) %s;\n" % (func_type, invoke)
244+
retcode = ""
260245

246+
wrap += invoke
247+
for argname in valist_args:
248+
wrap += "\t\t" + "va_end(%s);\n" % argname
249+
wrap += retcode
261250
wrap += "\t" + "}\n"
262251
wrap += "#endif\n"
263252

@@ -304,16 +293,11 @@ def marshall_defs(func_name, func_type, args):
304293
mrsh_name = "z_mrsh_" + func_name
305294

306295
nmrsh = 0 # number of marshalled uintptr_t parameter
307-
vrfy_parms = [] # list of (arg_num, mrsh_or_parm_num, bool_is_split)
308-
split_parms = [] # list of a (arg_num, mrsh_num) for each split
309-
for i, (argtype, _) in enumerate(args):
310-
if need_split(argtype):
311-
vrfy_parms.append((i, len(split_parms), True))
312-
split_parms.append((i, nmrsh))
313-
nmrsh += 2
314-
else:
315-
vrfy_parms.append((i, nmrsh, False))
316-
nmrsh += 1
296+
vrfy_parms = [] # list of (argtype, bool_is_split)
297+
for (argtype, _) in args:
298+
split = need_split(argtype)
299+
vrfy_parms.append((argtype, split))
300+
nmrsh += 2 if split else 1
317301

318302
# Final argument for a 64 bit return value?
319303
if need_split(func_type):
@@ -337,23 +321,20 @@ def marshall_defs(func_name, func_type, args):
337321
mrsh += ("\tZ_OOPS(Z_SYSCALL_MEMORY_READ(more, "
338322
+ str(nmrsh - 5) + " * sizeof(uintptr_t)));\n")
339323

340-
for i, split_rec in enumerate(split_parms):
341-
arg_num, mrsh_num = split_rec
342-
arg_type = args[arg_num][0]
343-
mrsh += "\t%s parm%d;\n" % (union_decl(arg_type), i)
344-
mrsh += "\t" + "parm%d.split.lo = %s;\n" % (i, mrsh_rval(mrsh_num,
345-
nmrsh))
346-
mrsh += "\t" + "parm%d.split.hi = %s;\n" % (i, mrsh_rval(mrsh_num + 1,
347-
nmrsh))
348-
# Finally, invoke the verify function
349-
out_args = []
350-
for i, argn, is_split in vrfy_parms:
351-
if is_split:
352-
out_args.append("parm%d.val" % argn)
324+
argnum = 0
325+
for i, (argtype, split) in enumerate(vrfy_parms):
326+
mrsh += "\t%s parm%d;\n" % (union_decl(argtype, split), i)
327+
if split:
328+
mrsh += "\t" + "parm%d.split.lo = %s;\n" % (i, mrsh_rval(argnum, nmrsh))
329+
argnum += 1
330+
mrsh += "\t" + "parm%d.split.hi = %s;\n" % (i, mrsh_rval(argnum, nmrsh))
353331
else:
354-
out_args.append("*(%s*)&%s" % (args[i][0], mrsh_rval(argn, nmrsh)))
332+
mrsh += "\t" + "parm%d.x = %s;\n" % (i, mrsh_rval(argnum, nmrsh))
333+
argnum += 1
355334

356-
vrfy_call = "z_vrfy_%s(%s)\n" % (func_name, ", ".join(out_args))
335+
# Finally, invoke the verify function
336+
out_args = ", ".join(["parm%d.val" % i for i in range(len(args))])
337+
vrfy_call = "z_vrfy_%s(%s)" % (func_name, out_args)
357338

358339
if func_type == "void":
359340
mrsh += "\t" + "%s;\n" % vrfy_call
@@ -499,19 +480,10 @@ def main():
499480
mrsh_fn = os.path.join(args.base_output, fn + "_mrsh.c")
500481

501482
with open(mrsh_fn, "w") as fp:
502-
fp.write("/* auto-generated by gen_syscalls.py, don't edit */\n")
503-
fp.write("#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)\n")
504-
fp.write("#pragma GCC diagnostic push\n")
505-
fp.write("#endif\n")
506-
fp.write("#ifdef __GNUC__\n")
507-
fp.write("#pragma GCC diagnostic ignored \"-Wstrict-aliasing\"\n")
508-
fp.write("#endif\n")
483+
fp.write("/* auto-generated by gen_syscalls.py, don't edit */\n\n")
509484
fp.write(mrsh_includes[fn] + "\n")
510485
fp.write("\n")
511486
fp.write(mrsh_defs[fn] + "\n")
512-
fp.write("#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)\n")
513-
fp.write("#pragma GCC diagnostic pop\n")
514-
fp.write("#endif\n")
515487

516488
if __name__ == "__main__":
517489
main()

0 commit comments

Comments
 (0)