>我设置了一个实验,看看它是否有效
int *p;
p[0] = 3;
我的想法是编译器给 p 一个随机值,我可以将其视为一个数组。
但事实证明分段错误,我不明白汇编代码。
0x0000000000401530 <+0>: push %rbp
0x0000000000401531 <+1>: mov %rsp,%rbp
0x0000000000401534 <+4>: sub $0x30,%rsp
0x0000000000401538 <+8>: mov %ecx,0x10(%rbp)
0x000000000040153b <+11>: mov %rdx,0x18(%rbp)
0x000000000040153f <+15>: callq 0x402170 <__main>
0x0000000000401544 <+20>: mov -0x8(%rbp),%rax
=> 0x0000000000401548 <+24>: movl $0x3,(%rax)
0x000000000040154e <+30>: mov $0x0,%eax
0x0000000000401553 <+35>: add $0x30,%rsp
0x0000000000401557 <+39>: pop %rbp
0x0000000000401558 <+40>: retq
我在谷歌上搜索,mov是英特尔风格,movl是AT&T风格。这两种风格是如何结合在一起的?
在这一行:
mov -0x8(%rbp),%rax
似乎将地址 rbp-0x8 的值移动到注册 rax,对吧? 这个"-0x8(%rbp("是随机值到p吗?
我认为 %rax 不是 p,因为在下一行 CPU 给 %rax 0x3 美元。似乎 %rax 是数组的第一个内存。
如何解释此汇编代码?谢谢。
我不是汇编专家,但代码看起来很清楚,所以我会尝试解释它。
0x0000000000401530 <+0>: push %rbp
0x0000000000401531 <+1>: mov %rsp,%rbp
(上图(这是编译器在未启用优化时生成的一些"过程"代码。它保存RSP(64位堆栈指针(的状态。
0x0000000000401534 <+4>: sub $0x30,%rsp
这会在堆栈上为局部变量保留额外的空间。
0x0000000000401538 <+8>: mov %ecx,0x10(%rbp)
0x000000000040153b <+11>: mov %rdx,0x18(%rbp)
这会将 argc 和 argv 保存到堆栈上,因为您进行了调试构建,因此所有变量都必须在内存中。 (它使用的空间位于返回地址上方,main
的调用方保留空间。 这称为影子空间,是 Windows x64 调用约定的一项功能。
0x000000000040153f <+15>: callq 0x402170 <__main>
这调用某种早期初始化函数。 它可能使用 argc 和 argv(仍在寄存器中(,也可能不使用;我们无法从代码中分辨出来。
0x0000000000401544 <+20>: mov -0x8(%rbp),%rax
这会将未初始化的堆栈内存加载为int *p
的值。 自动变量放置在堆栈上。 您读取p
而没有先编写它,编译器只是读取它为int *p;
选择的堆栈槽中已经存在的任何垃圾或零
=> 0x0000000000401548 <+24>: movl $0x3,(%rax)
此行将rax
指向的地址设置为即时值 3。
p
的值在rax
,所以这是你的p[0] = 3;
,取消引用p
持有的任何垃圾。 您崩溃是因为它没有碰巧指向可写内存。 (覆盖内存中的一些随机dword几乎不会更好,但至少你的代码不会在这里崩溃,只是可能在以后的某个时候,如果p
的垃圾值恰好是一个有效的指针。
0x000000000040154e <+30>: mov $0x0,%eax
这会将寄存器eax
设置为零,并有效地将寄存器rax
设置为零。 Windows x64(与每个标准调用约定一样(使用 RAX 作为返回值,因此这是在main
底部实现隐式return 0;
。
0x0000000000401553 <+35>: add $0x30,%rsp
0x0000000000401557 <+39>: pop %rbp
0x0000000000401558 <+40>: retq
将指针状态还原到函数调用之前。