Mac平台x86-64汇编syscall错误检查失效问题排查
问题描述
我在Mac平台编写x86-64汇编代码,通过syscall获取当前时间,原本运行正常。添加错误检查时,反汇编了gettimeofday包装函数,发现它在syscall后用jae指令处理成功结果——查资料知道jae是jnc的别名,于是我用jc指令做错误检查(进位标志置位则跳转到错误逻辑)。但出现异常:
- 第二次执行syscall时进位标志被置位,但仍成功获取了新时间;
- 添加将当前时间加5秒存入rdx的代码后,syscall后的
jc会触发错误跳转,此时rax值为0xe(15),但curtime(%rip)仍正常更新。
想知道哪里出错了,为什么mov和add指令会影响错误检查?
反汇编的gettimeofday包装函数
___gettimeofday: 4969: 48 c7 c2 00 00 00 00 movq $0x0, %rdx 4970: b8 74 00 00 02 movl $0x2000074, %eax 4975: 49 89 ca movq %rcx, %r10 4978: 0f 05 syscall 497a: 73 08 jae 0x4984 497c: 48 89 c7 movq %rax, %rdi 497f: e9 41 d8 ff ff jmp _cerror_nocancel 4984: 48 83 f8 00 cmpq $0x0, %rax 4988: 74 08 je 0x4992 498a: 48 89 07 movq %rax, (%rdi) 498d: 89 57 08 movl %edx, 0x8(%rdi) 4990: 31 c0 xorl %eax, %eax 4992: c3 retq
问题复现代码
# get current time movq $0x2000074, %rax leaq curtime(%rip), %rdi syscall jc error # get current time again movq $0x2000074, %rax leaq curtime(%rip), %rdi syscall jc error # add 5 seconds to the start time and stash in rdx movq curtime(%rip), %rdx addq $5, %rdx movq $0x2000074, %rax leaq curtime(%rip), %rdi syscall jc error
问题原因
你忽略了macOS x86-64 syscall的两个核心调用约定,导致错误标志位异常:
未正确设置rdx寄存器
gettimeofday的第二个参数是时区指针,通常传NULL(0)。你的代码前两次调用时rdx是未初始化的随机值,虽然内核可能容错处理,但会导致标志位状态不可预测;第三次调用时,你把rdx设置成了加5后的时间值(无效指针),内核检测到参数非法,返回错误码0xe(EFAULT地址错误)并置位CF。但内核已经完成了第一个参数(时间结构)的写入,所以curtime(%rip)仍能正常更新。未保存rcx到r10
macOS的syscall指令会将当前用户态RIP存入rcx,内核需要这个值来正确返回用户态,因此要求调用前必须把rcx的值移动到r10。你的代码跳过了这一步,导致内核返回逻辑破坏标志位,出现第二次调用CF置位但实际成功的异常情况。
解决方法
严格按照macOS syscall约定准备寄存器,修改后的代码如下:
# get current time movq $0x2000074, %rax leaq curtime(%rip), %rdi movq $0, %rdx # 第二个参数传NULL movq %rcx, %r10 # 保存rcx到r10,符合macOS syscall约定 syscall jc error # get current time again movq $0x2000074, %rax leaq curtime(%rip), %rdi movq $0, %rdx movq %rcx, %r10 syscall jc error # add 5 seconds to the start time and stash in rdx movq curtime(%rip), %rdx addq $5, %rdx movq $0x2000074, %rax leaq curtime(%rip), %rdi movq $0, %rdx # 重新设置rdx为NULL,覆盖之前的时间值 movq %rcx, %r10 syscall jc error
额外说明:macOS syscall成功时CF清零,失败时CF置位且rax返回错误码,jae/jnc判断成功分支是正确的,但必须严格遵守寄存器约定,否则标志位和返回值都会出现异常。
内容的提问来源于stack exchange,提问作者Chris

