Why is ebx saved in the stack frame of a simple function, calling gets?

I am trying to write a buffer overflow exercise in c for students.

Normally the stack frame consists of the function parameters, the return address, the base pointer and local variables. But I detected, that sometimes additional registers are saved along with the base pointer. I remember from class that calee saved registers have to be saved before they are used. But there are cases where the compilation of C code produces assembly, which saves and uses registers apperently without purpose. Please explain this behaviour to me.

Assume the main function

int main (int argc, char** argv) {
    func();

    return 0;
}

and the function

void func() {
    char buf[5];
    strcpy(buf,"AAAA");
    strcpy(buf,"BBBB");
}

If I debug the resulting executable with gdb

break func
run
info frame

everything is fine and the stack frame only contains ebp and eip.

If I use

void func() {
    char buf[5];
    gets(buf);
}

I get

 Saved registers:
  ebx at 0xffffd1cc, ebp at 0xffffd1d0, eip at 0xffffd1d4

So ebx is additionally saved in the stack frame? Why? If I run

disas func

I get

Dump of assembler code for function func:
   0x56555730 <+0>: push   %ebp
   0x56555731 <+1>: mov    %esp,%ebp
   0x56555733 <+3>: push   %ebx
   0x56555734 <+4>: sub    $0x8,%esp
   0x56555737 <+7>: call   0x5655576e <__x86.get_pc_thunk.ax>
   0x5655573c <+12>:    add    $0x18c4,%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    $0x4,%esp
   0x5655574f <+31>:    nop
   0x56555750 <+32>:    mov    -0x4(%ebp),%ebx
   0x56555753 <+35>:    leave  
   0x56555754 <+36>:    ret    
End of assembler dump.

So ebx is saved. Ok. But what is it used for? eax is moved in ebx before calling gets(). But it is not used afterwards. The old ebx is just restored from the stack before leaving and returning. It seems useless. Btw. Whats the whole call get_pc_thunk stuff?

Comparable behaviour, if I use printf instead of gets:

void func() {
    char buf[5];
    strcpy(buf, "AAAA");
    printf("%s",buf);
}

gdb output:

(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    $0x8,%esp
   0x56555737 <+7>: call   0x56555780 <__x86.get_pc_thunk.ax>
   0x5655573c <+12>:    add    $0x18c4,%eax
=> 0x56555741 <+17>:    movl   $0x41414141,-0x9(%ebp)
   0x56555748 <+24>:    movb   $0x0,-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    $0x8,%esp
   0x56555761 <+49>:    nop
   0x56555762 <+50>:    mov    -0x4(%ebp),%ebx
   0x56555765 <+53>:    leave  
   0x56555766 <+54>:    ret    
End of assembler dump.

Can someone please explain this to me?

I use cmake for compiling with the following 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 https://stackoverflow.com/questions/1061818/stack-allocation-padding-and-alignment
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 seems to use gcc.


Your compiler toolchain has been configured (probably by your distro) to produce Position-Independent Executables (PIE) by default. On 32-bit x86, in order for position-independent code to call functions that may reside in a different library from the caller, the address of the calling module's GOT must be loaded into ebx at call time; this is an ABI requirement. Since ebx is a call-saved register in the x86 ABI, the caller must save and later restore it before returning to its own caller.

This article I wrote on the topic a while back may be informative:

https://ewontfix.com/18/

On very recent versions of gcc, the new -fno-plt option may avoid this issue by inlining the load from the GOT rather than using the PLT which depends on ebx .

链接地址: http://www.djcxy.com/p/84354.html

上一篇: 堆栈中局部变量的X86空间分配

下一篇: 为什么ebx保存在一个简单函数的栈帧中,调用get?