AOSP 非显而易见的 syscall() 实现
AOSP non-obvious syscall() implementation
据我所知 Linux ARM 的 ABI 声明系统调用 return 值是通过 r0
传递的,如果它是负数,则应该将其作为 errno 值取反进行线程处理。即系统调用以一些错误结束。
AOSP does this check 以某种奇特的方式:
ENTRY(syscall)
mov ip, sp
stmfd sp!, {r4, r5, r6, r7}
.cfi_def_cfa_offset 16
.cfi_rel_offset r4, 0
.cfi_rel_offset r5, 4
.cfi_rel_offset r6, 8
.cfi_rel_offset r7, 12
mov r7, r0
mov r0, r1
mov r1, r2
mov r2, r3
ldmfd ip, {r3, r4, r5, r6}
swi #0
ldmfd sp!, {r4, r5, r6, r7}
.cfi_def_cfa_offset 0
cmn r0, #(MAX_ERRNO + 1) /* Set C flag if r0 is between -4095 and -1, set Z flag if r0 == -4096 */
bxls lr /* return if Z is set or C is clear */
neg r0, r0
b __set_errno_internal
END(syscall)
看起来此函数不会威胁 r0 < -4096
作为错误条件。虽然它应该。当然,真正的 errno
将适合允许的间隙,但无论如何,以这种方式执行检查而不是仅仅测试 r0
是否存在负面影响的理由是什么?
P.S。如果我遗漏了什么或者只是我的分析不正确 - 任何评论表示赞赏。
对 errno 值的 Apper 限制 4095 是有意的,在代码中使用这些知识是可以的。实际上,这修改了价值模型,由系统调用和一些其他内核函数返回,到:
[-4095 ... -1]
- 错误
- 其他值 - 结果
此模型允许更多可能的结果值。例如,如果将结果解释为无符号长整数,则结果的值范围从 [0... (MAX/2)]
扩展到 [0 ... (MAX - 4096)]
。这很有用,例如当结果是指针时,请参阅 IS_ERR
宏的 implementation。
通常情况下,mmap
的 return 值如果解释为有符号数,则为负数。
Linux的system-callABI/API就是这样设计的,只允许有限范围的-errno
值,避免用完一半的范围一小组错误代码的可能 return 值。 (没有人需要超过 4095 个错误代码吗?)
正如@Timothy 指出的那样,mmap
是一个很好的例子:它需要 return 一个指针。 mmap
的 return 值始终为 page-aligned,因此在具有 4k 页或更大页面(即几乎(?)所有内容)的系统上,-4095
是最大负数您可以在不使用有效页面地址的情况下使用。
-4095
= 2^32 - 4095
在 32 位 2 的补码系统中。最高page-start地址是2^32-4096
。这就是选择 this 范围作为“错误”范围的明显动机。
(但请注意,您仍然不能映射虚拟地址 space、even for a 32-bit process under a 64-bit kernel 的最高页,即使 -4096UL
将是 non-error return 来自 mmap
。内核 内部 使用相同的 in-band -errno
指针代码机制,而不仅仅是 system-call ABI,因此它保留了首页,以防止出现同时也是错误代码的有效指针。这也避免了极端情况的可能性,例如指向数组 one-past-end 的指针为 0(NULL)。)
意味着错误的值的范围在理论上是 platform-dependent 和系统调用 ABI 的一部分,但 AFAIK Linux 在它支持的每个平台上使用 [-4095..-1]
。非 x86 或 ARM-specific 的 C 源文件中的 MUSL libc 实现 hard-codes -4096UL
:/src/internal/syscall_ret.c
.
正如@Tsyvarev 指出的那样,Linux 将 MAX_ERRNO
定义为 4095 in include/linux/err.h
,而不是 arch-specific header. (一条评论指出,如果需要的话,它可以被制作成 arch-specific,并且它是根据内核指针的工作方式而特别选择的。)
x86-64 System V ABI 也有一个关于 Linux 系统调用的信息性(非规范性)部分,其中指定了 -4095
到 -1
的范围。 (Search for -4095
). (Other platforms using x86-64 System V can vary their syscall ABI, like FreeBSD 或 MacOS)
有趣的事实:这意味着 sys_getpriority
不能直接 return nice
级别。相反 it returns 20-nice
, which maps the -20..19
range of nice values to 40..1
。 libc 包装函数必须在通用 retval > -4096UL
检查检测到 non-error return 值后解码 nice 值。
有趣的事实 #2:某些 Linux 平台(例如 MIPS 和 PowerPC)上的系统调用 ABI returns -1
在 return 值和 errno 代码中在一个单独的寄存器中。资料来源:syscall inline-asm wrapper library 中的评论。 (看起来是一个不错的库;避免了某些系统调用包装器 header 所具有的缺失 "memory"
破坏等问题。)
相关:
- What are the return values of system calls in Assembly?
- Linux System Calls, Error Numbers, and In-Band Signaling(使用 x86-64 示例,但概念当然是相同的)。这里的关键概念是 In-Band 信号.
- The Definitive Guide to Linux System Calls (on x86)
据我所知 Linux ARM 的 ABI 声明系统调用 return 值是通过 r0
传递的,如果它是负数,则应该将其作为 errno 值取反进行线程处理。即系统调用以一些错误结束。
AOSP does this check 以某种奇特的方式:
ENTRY(syscall)
mov ip, sp
stmfd sp!, {r4, r5, r6, r7}
.cfi_def_cfa_offset 16
.cfi_rel_offset r4, 0
.cfi_rel_offset r5, 4
.cfi_rel_offset r6, 8
.cfi_rel_offset r7, 12
mov r7, r0
mov r0, r1
mov r1, r2
mov r2, r3
ldmfd ip, {r3, r4, r5, r6}
swi #0
ldmfd sp!, {r4, r5, r6, r7}
.cfi_def_cfa_offset 0
cmn r0, #(MAX_ERRNO + 1) /* Set C flag if r0 is between -4095 and -1, set Z flag if r0 == -4096 */
bxls lr /* return if Z is set or C is clear */
neg r0, r0
b __set_errno_internal
END(syscall)
看起来此函数不会威胁 r0 < -4096
作为错误条件。虽然它应该。当然,真正的 errno
将适合允许的间隙,但无论如何,以这种方式执行检查而不是仅仅测试 r0
是否存在负面影响的理由是什么?
P.S。如果我遗漏了什么或者只是我的分析不正确 - 任何评论表示赞赏。
对 errno 值的 Apper 限制 4095 是有意的,在代码中使用这些知识是可以的。实际上,这修改了价值模型,由系统调用和一些其他内核函数返回,到:
[-4095 ... -1]
- 错误- 其他值 - 结果
此模型允许更多可能的结果值。例如,如果将结果解释为无符号长整数,则结果的值范围从 [0... (MAX/2)]
扩展到 [0 ... (MAX - 4096)]
。这很有用,例如当结果是指针时,请参阅 IS_ERR
宏的 implementation。
通常情况下,mmap
的 return 值如果解释为有符号数,则为负数。
Linux的system-callABI/API就是这样设计的,只允许有限范围的-errno
值,避免用完一半的范围一小组错误代码的可能 return 值。 (没有人需要超过 4095 个错误代码吗?)
正如@Timothy 指出的那样,mmap
是一个很好的例子:它需要 return 一个指针。 mmap
的 return 值始终为 page-aligned,因此在具有 4k 页或更大页面(即几乎(?)所有内容)的系统上,-4095
是最大负数您可以在不使用有效页面地址的情况下使用。
-4095
= 2^32 - 4095
在 32 位 2 的补码系统中。最高page-start地址是2^32-4096
。这就是选择 this 范围作为“错误”范围的明显动机。
(但请注意,您仍然不能映射虚拟地址 space、even for a 32-bit process under a 64-bit kernel 的最高页,即使 -4096UL
将是 non-error return 来自 mmap
。内核 内部 使用相同的 in-band -errno
指针代码机制,而不仅仅是 system-call ABI,因此它保留了首页,以防止出现同时也是错误代码的有效指针。这也避免了极端情况的可能性,例如指向数组 one-past-end 的指针为 0(NULL)。)
意味着错误的值的范围在理论上是 platform-dependent 和系统调用 ABI 的一部分,但 AFAIK Linux 在它支持的每个平台上使用 [-4095..-1]
。非 x86 或 ARM-specific 的 C 源文件中的 MUSL libc 实现 hard-codes -4096UL
:/src/internal/syscall_ret.c
.
正如@Tsyvarev 指出的那样,Linux 将 MAX_ERRNO
定义为 4095 in include/linux/err.h
,而不是 arch-specific header. (一条评论指出,如果需要的话,它可以被制作成 arch-specific,并且它是根据内核指针的工作方式而特别选择的。)
x86-64 System V ABI 也有一个关于 Linux 系统调用的信息性(非规范性)部分,其中指定了 -4095
到 -1
的范围。 (Search for -4095
). (Other platforms using x86-64 System V can vary their syscall ABI, like FreeBSD 或 MacOS)
有趣的事实:这意味着 sys_getpriority
不能直接 return nice
级别。相反 it returns 20-nice
, which maps the -20..19
range of nice values to 40..1
。 libc 包装函数必须在通用 retval > -4096UL
检查检测到 non-error return 值后解码 nice 值。
有趣的事实 #2:某些 Linux 平台(例如 MIPS 和 PowerPC)上的系统调用 ABI returns -1
在 return 值和 errno 代码中在一个单独的寄存器中。资料来源:syscall inline-asm wrapper library 中的评论。 (看起来是一个不错的库;避免了某些系统调用包装器 header 所具有的缺失 "memory"
破坏等问题。)
相关:
- What are the return values of system calls in Assembly?
- Linux System Calls, Error Numbers, and In-Band Signaling(使用 x86-64 示例,但概念当然是相同的)。这里的关键概念是 In-Band 信号.
- The Definitive Guide to Linux System Calls (on x86)