我有以下代码:
void cp(void *a, const void *b, int n)
{
for (int i = 0; i
如果使用进行编译,则会gcc -nostdlib -O1 "./a.c" -o "./a"
得到一个可运行的程序,但是如果使用进行编译,则会-O2
得到一个生成分段错误的程序。
这是使用以下代码生成的代码-O1
:
0000000000001000 :
1000: b8 00 00 00 00 mov $0x0,%eax
1005: 0f b6 14 06 movzbl (%rsi,%rax,1),%edx
1009: 88 14 07 mov %dl,(%rdi,%rax,1)
100c: 48 83 c0 01 add $0x1,%rax
1010: 48 83 f8 0f cmp $0xf,%rax
1014: 75 ef jne 1005
1016: c3 retq
0000000000001017 <_start>:
1017: 48 83 ec 30 sub $0x30,%rsp
101b: 48 b8 31 32 33 34 35 movabs $0x3837363534333231,%rax
1022: 36 37 38
1025: 48 ba 39 30 31 32 33 movabs $0x35343332313039,%rdx
102c: 34 35 00
102f: 48 89 04 24 mov %rax,(%rsp)
1033: 48 89 54 24 08 mov %rdx,0x8(%rsp)
1038: 48 89 e6 mov %rsp,%rsi
103b: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi
1040: ba 0f 00 00 00 mov $0xf,%edx
1045: e8 b6 ff ff ff callq 1000
104a: b8 3c 00 00 00 mov $0x3c,%eax
104f: bf 00 00 00 00 mov $0x0,%edi
1054: 0f 05 syscall
这是使用以下代码生成的代码-O2
:
0000000000001000 :
1000: 31 c0 xor %eax,%eax
1002: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
1008: 0f b6 14 06 movzbl (%rsi,%rax,1),%edx
100c: 88 14 07 mov %dl,(%rdi,%rax,1)
100f: 48 83 c0 01 add $0x1,%rax
1013: 48 83 f8 0f cmp $0xf,%rax
1017: 75 ef jne 1008
1019: c3 retq
101a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
0000000000001020 <_start>:
1020: 48 8d 44 24 d8 lea -0x28(%rsp),%rax
1025: 48 8d 54 24 c9 lea -0x37(%rsp),%rdx
102a: b9 31 00 00 00 mov $0x31,%ecx
102f: 66 0f 6f 05 c9 0f 00 movdqa 0xfc9(%rip),%xmm0 # 2000 <_start+0xfe0>
1036: 00
1037: 48 8d 70 0f lea 0xf(%rax),%rsi
103b: 0f 29 44 24 c8 movaps %xmm0,-0x38(%rsp)
1040: eb 0d jmp 104f <_start+0x2f>
1042: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
1048: 0f b6 0a movzbl (%rdx),%ecx
104b: 48 83 c2 01 add $0x1,%rdx
104f: 88 08 mov %cl,(%rax)
1051: 48 83 c0 01 add $0x1,%rax
1055: 48 39 f0 cmp %rsi,%rax
1058: 75 ee jne 1048 <_start+0x28>
105a: b8 3c 00 00 00 mov $0x3c,%eax
105f: 31 ff xor %edi,%edi
1061: 0f 05 syscall
飞机坠毁发生在103b
指示处movaps %xmm0,-0x38(%rsp)
。
我注意到,如果m
包含少于15个字符,则生成的代码将不同,并且不会发生崩溃。
我究竟做错了什么?
1> Peter Cordes..:
_start
不是功能。 它不会被任何东西调用,并且在进入时栈是16字节对齐的,而不是(按照ABI的要求)距离16字节对齐8字节。
(ABI在a之前需要16字节对齐call
,并call
推送8字节返回地址。因此,在函数项RSP-8和RSP + 8上是16字节对齐。)
在-O2
GCC中,使用需要对齐的16字节指令来实现的复制cp()
,以"123456789012345"
将从静态存储复制到堆栈。
在-O1
,GCC仅使用两条mov r64, imm64
指令将字节存储到8字节存储的整数reg中。这些不需要对齐。
main
如果您希望一切正常,只需像普通人一样用C 编写C即可。
或者,如果您要尝试在asm中对某些轻量级的东西进行微基准测试,则可以尝试
asm("push %rax");
在最高端_start
将RSP修改为8,在此之前,GCC希望先运行它,然后再对堆栈进行其他操作。GNU C Basic的asm语句是隐式的,volatile
因此您不需要asm volatile
,尽管这不会造成伤害。
您自己是100%的,并负责使用适用于您使用的任何优化级别的内联asm正确地欺骗编译器。
一种更安全的方法是编写自己的轻量级_start
调用main:
// at global scope:
asm(
".globl _start \n"
"_start: \n"
" mov (%rsp), %rdi \n" // argc
" lea 8(%rsp), %rsi \n" // argv
" lea 8(%rsi, %rdi, 8), %rdx \n" // envp
" call main \n"
// NOT DONE: stdio cleanup or other atexit stuff
// DO NOT USE WITH GLIBC; use libc's CRT code if you use libc
" mov %eax, %edi \n"
" mov $231, %eax \n"
" syscall" // exit_group( main() )
);
int main(int argc, char**argv, char**envp) {
... your code here
return 0;
}
如果您不想main
返回,可以pop %rdi
; mov %rsp, %rsi
; jmp main
给它argc和argv而没有返回地址。
另请参阅:如何在不使用Glibc的情况下使用C中的内联汇编获取参数值?对于其他手卷_start
版本;这很像@zwol在那。