为什么ebx保存在调用gets的简单函数的栈帧中?
Why is ebx saved in the stack frame of a simple function, calling gets?
我正在尝试用 c 语言为学生编写缓冲区溢出练习。
通常堆栈帧由函数参数、return地址、基指针和局部变量组成。但我发现,有时附加寄存器会与基指针一起保存。我记得在 class 中,calee 保存的寄存器必须在使用前保存。但也有C代码编译产生汇编的情况,表面上无目的地保存和使用寄存器。请向我解释这种行为。
承担主要职能
int main (int argc, char** argv) {
func();
return 0;
}
和函数
void func() {
char buf[5];
strcpy(buf,"AAAA");
strcpy(buf,"BBBB");
}
如果我用 gdb 调试生成的可执行文件
break func
run
info frame
一切正常,堆栈帧只包含 ebp 和 eip。
如果我用
void func() {
char buf[5];
gets(buf);
}
我明白了
Saved registers:
ebx at 0xffffd1cc, ebp at 0xffffd1d0, eip at 0xffffd1d4
那么栈帧中额外保存了ebx?为什么?如果我运行
disas func
我明白了
Dump of assembler code for function func:
0x56555730 <+0>: push %ebp
0x56555731 <+1>: mov %esp,%ebp
0x56555733 <+3>: push %ebx
0x56555734 <+4>: sub [=16=]x8,%esp
0x56555737 <+7>: call 0x5655576e <__x86.get_pc_thunk.ax>
0x5655573c <+12>: add [=16=]x18c4,%eax
=> 0x56555741 <+17>: lea -0x9(%ebp),%edx
0x56555744 <+20>: push %edx
0x56555745 <+21>: mov %eax,%ebx
0x56555747 <+23>: call 0x56555590 <gets@plt>
0x5655574c <+28>: add [=16=]x4,%esp
0x5655574f <+31>: nop
0x56555750 <+32>: mov -0x4(%ebp),%ebx
0x56555753 <+35>: leave
0x56555754 <+36>: ret
End of assembler dump.
所以ebx得救了。行。但是它有什么用呢?在调用 gets() 之前将 eax 移入 ebx。但是之后就不用了。旧的 ebx 在离开和 returning 之前刚刚从堆栈中恢复。好像没用。
顺便提一句。全部 call get_pc_thunk
是什么东西?
类似的行为,如果我使用 printf 而不是 gets:
void func() {
char buf[5];
strcpy(buf, "AAAA");
printf("%s",buf);
}
gdb 输出:
(gdb) info frame
Stack level 0, frame at 0xffffd1d8:
eip = 0x56555741 in func (/home/mischa/stuff/test/test.c:35); saved eip = 0x56555779
called by frame at 0xffffd1e0
source language c.
Arglist at 0xffffd1d0, args:
Locals at 0xffffd1d0, Previous frame's sp is 0xffffd1d8
Saved registers:
ebx at 0xffffd1cc, ebp at 0xffffd1d0, eip at 0xffffd1d4
(gdb) disas func
Dump of assembler code for function func:
0x56555730 <+0>: push %ebp
0x56555731 <+1>: mov %esp,%ebp
0x56555733 <+3>: push %ebx
0x56555734 <+4>: sub [=18=]x8,%esp
0x56555737 <+7>: call 0x56555780 <__x86.get_pc_thunk.ax>
0x5655573c <+12>: add [=18=]x18c4,%eax
=> 0x56555741 <+17>: movl [=18=]x41414141,-0x9(%ebp)
0x56555748 <+24>: movb [=18=]x0,-0x5(%ebp)
0x5655574c <+28>: lea -0x9(%ebp),%edx
0x5655574f <+31>: push %edx
0x56555750 <+32>: lea -0x17f0(%eax),%edx
0x56555756 <+38>: push %edx
0x56555757 <+39>: mov %eax,%ebx
0x56555759 <+41>: call 0x565555a0 <printf@plt>
0x5655575e <+46>: add [=18=]x8,%esp
0x56555761 <+49>: nop
0x56555762 <+50>: mov -0x4(%ebp),%ebx
0x56555765 <+53>: leave
0x56555766 <+54>: ret
End of assembler dump.
谁能给我解释一下吗?
我使用 cmake 编译以下 CMakeLists.txt:
cmake_minimum_required (VERSION 2.8)
# projectname is the same as the main-executable
project(test)
# compile with 32 bit
add_definitions('-m32')
# Disable compiler optimization
add_definitions('-O0')
# include debugging information
add_definitions('-g')
# Align items on the stack to 4 bytes. This makes stuff easier.
# See
add_definitions('-mpreferred-stack-boundary=2')
# disable compiler buffer overflow protection
add_definitions('-z execstack -z norelro -fno-stack-protector')
# executable source code
add_executable(test test.c)
cmake 似乎使用了 gcc。
您的编译器工具链已配置(可能由您的发行版配置)以默认生成 Position-Independent 可执行文件 (PIE)。在 32 位 x86 上,为了 position-independent 代码调用可能驻留在与调用者不同的库中的函数,调用模块的 GOT 地址必须在调用时加载到 ebx
;这是 ABI 要求。由于 ebx
是 x86 ABI 中的 call-saved 寄存器,调用者必须保存它并稍后在返回给它自己的调用者之前恢复它。
我不久前写的这篇文章可能会提供信息:
在最新版本的 gcc 上,新的 -fno-plt
选项可以通过从 GOT 内联加载而不是使用依赖于 ebx
.
的 PLT 来避免这个问题
我正在尝试用 c 语言为学生编写缓冲区溢出练习。
通常堆栈帧由函数参数、return地址、基指针和局部变量组成。但我发现,有时附加寄存器会与基指针一起保存。我记得在 class 中,calee 保存的寄存器必须在使用前保存。但也有C代码编译产生汇编的情况,表面上无目的地保存和使用寄存器。请向我解释这种行为。
承担主要职能
int main (int argc, char** argv) {
func();
return 0;
}
和函数
void func() {
char buf[5];
strcpy(buf,"AAAA");
strcpy(buf,"BBBB");
}
如果我用 gdb 调试生成的可执行文件
break func
run
info frame
一切正常,堆栈帧只包含 ebp 和 eip。
如果我用
void func() {
char buf[5];
gets(buf);
}
我明白了
Saved registers:
ebx at 0xffffd1cc, ebp at 0xffffd1d0, eip at 0xffffd1d4
那么栈帧中额外保存了ebx?为什么?如果我运行
disas func
我明白了
Dump of assembler code for function func:
0x56555730 <+0>: push %ebp
0x56555731 <+1>: mov %esp,%ebp
0x56555733 <+3>: push %ebx
0x56555734 <+4>: sub [=16=]x8,%esp
0x56555737 <+7>: call 0x5655576e <__x86.get_pc_thunk.ax>
0x5655573c <+12>: add [=16=]x18c4,%eax
=> 0x56555741 <+17>: lea -0x9(%ebp),%edx
0x56555744 <+20>: push %edx
0x56555745 <+21>: mov %eax,%ebx
0x56555747 <+23>: call 0x56555590 <gets@plt>
0x5655574c <+28>: add [=16=]x4,%esp
0x5655574f <+31>: nop
0x56555750 <+32>: mov -0x4(%ebp),%ebx
0x56555753 <+35>: leave
0x56555754 <+36>: ret
End of assembler dump.
所以ebx得救了。行。但是它有什么用呢?在调用 gets() 之前将 eax 移入 ebx。但是之后就不用了。旧的 ebx 在离开和 returning 之前刚刚从堆栈中恢复。好像没用。
顺便提一句。全部 call get_pc_thunk
是什么东西?
类似的行为,如果我使用 printf 而不是 gets:
void func() {
char buf[5];
strcpy(buf, "AAAA");
printf("%s",buf);
}
gdb 输出:
(gdb) info frame
Stack level 0, frame at 0xffffd1d8:
eip = 0x56555741 in func (/home/mischa/stuff/test/test.c:35); saved eip = 0x56555779
called by frame at 0xffffd1e0
source language c.
Arglist at 0xffffd1d0, args:
Locals at 0xffffd1d0, Previous frame's sp is 0xffffd1d8
Saved registers:
ebx at 0xffffd1cc, ebp at 0xffffd1d0, eip at 0xffffd1d4
(gdb) disas func
Dump of assembler code for function func:
0x56555730 <+0>: push %ebp
0x56555731 <+1>: mov %esp,%ebp
0x56555733 <+3>: push %ebx
0x56555734 <+4>: sub [=18=]x8,%esp
0x56555737 <+7>: call 0x56555780 <__x86.get_pc_thunk.ax>
0x5655573c <+12>: add [=18=]x18c4,%eax
=> 0x56555741 <+17>: movl [=18=]x41414141,-0x9(%ebp)
0x56555748 <+24>: movb [=18=]x0,-0x5(%ebp)
0x5655574c <+28>: lea -0x9(%ebp),%edx
0x5655574f <+31>: push %edx
0x56555750 <+32>: lea -0x17f0(%eax),%edx
0x56555756 <+38>: push %edx
0x56555757 <+39>: mov %eax,%ebx
0x56555759 <+41>: call 0x565555a0 <printf@plt>
0x5655575e <+46>: add [=18=]x8,%esp
0x56555761 <+49>: nop
0x56555762 <+50>: mov -0x4(%ebp),%ebx
0x56555765 <+53>: leave
0x56555766 <+54>: ret
End of assembler dump.
谁能给我解释一下吗?
我使用 cmake 编译以下 CMakeLists.txt:
cmake_minimum_required (VERSION 2.8)
# projectname is the same as the main-executable
project(test)
# compile with 32 bit
add_definitions('-m32')
# Disable compiler optimization
add_definitions('-O0')
# include debugging information
add_definitions('-g')
# Align items on the stack to 4 bytes. This makes stuff easier.
# See
add_definitions('-mpreferred-stack-boundary=2')
# disable compiler buffer overflow protection
add_definitions('-z execstack -z norelro -fno-stack-protector')
# executable source code
add_executable(test test.c)
cmake 似乎使用了 gcc。
您的编译器工具链已配置(可能由您的发行版配置)以默认生成 Position-Independent 可执行文件 (PIE)。在 32 位 x86 上,为了 position-independent 代码调用可能驻留在与调用者不同的库中的函数,调用模块的 GOT 地址必须在调用时加载到 ebx
;这是 ABI 要求。由于 ebx
是 x86 ABI 中的 call-saved 寄存器,调用者必须保存它并稍后在返回给它自己的调用者之前恢复它。
我不久前写的这篇文章可能会提供信息:
在最新版本的 gcc 上,新的 -fno-plt
选项可以通过从 GOT 内联加载而不是使用依赖于 ebx
.