Помощь в рисовании стека с использованием кода C и кода сборки - PullRequest
2 голосов
/ 12 октября 2011

Я пытаюсь нарисовать стек так, как он выглядит перед строкой «return count» в функции secondCall.Я пытаюсь нарисовать его так, чтобы он показывал все три кадра (или записи активации) для трех активных функций, main, firstCall и secondCall.

Кто-нибудь поможет мне составить диаграмму стека?Я пытаюсь нарисовать позиции указателей base (ebp) и stack (esp) так, как они были в каждом кадре стека перед вызовом следующей функции.

Код C выглядит следующим образом:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>


int secondCall(int a, int b) {
  int count;
  count = write(STDOUT_FILENO, &"hello\n", 6);
  count += write(STDOUT_FILENO, &"jbnd007\n", 8);
  count += a + b;
  return count;
}
int firstCall(void) {
  int local;
  local = secondCall(4, 2);
  return local;
}
int main(int argc, char** argv) {
  int result;
  result = firstCall();
  return (EXIT_SUCCESS);
}

Код сборки выглядит следующим образом:

    .file   "A3Program2.c"
    .section    .rodata
.LC0:
    .string "hello\n"
.LC1:
    .string "jbnd007\n"
    .text
.globl secondCall
    .type   secondCall, @function
secondCall:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $40, %esp
    movl    $6, 8(%esp)
    movl    $.LC0, 4(%esp)
    movl    $1, (%esp)
    call    write
    movl    %eax, -12(%ebp)
    movl    $8, 8(%esp)
    movl    $.LC1, 4(%esp)
    movl    $1, (%esp)
    call    write
    addl    %eax, -12(%ebp)
    movl    12(%ebp), %eax
    movl    8(%ebp), %edx
    leal    (%edx,%eax), %eax
    addl    %eax, -12(%ebp)
    movl    -12(%ebp), %eax
    leave
    ret
    .size   secondCall, .-secondCall
.globl firstCall
    .type   firstCall, @function
firstCall:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $40, %esp
    movl    $2, 4(%esp)
    movl    $4, (%esp)
    call    secondCall
    movl    %eax, -12(%ebp)
    movl    -12(%ebp), %eax
    leave
    ret
    .size   firstCall, .-firstCall
.globl main
    .type   main, @function
main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    subl    $16, %esp
    call    firstCall
    movl    %eax, 12(%esp)
    movl    $0, %eax
    leave
    ret
    .size   main, .-main
    .ident  "GCC: (Ubuntu/Linaro 4.4.4-14ubuntu5) 4.4.5"
    .section    .note.GNU-stack,"",@progbits

Рисунок стека, который сейчас у меня есть:

+------------------------------+ high address
| original position of stack pointer
+------------------------------+
| saved value of ebp <- ebp (base pointer when in main)
+------------------------------+
| alignment spacing (don’t really know how big until runtime)
+------------------------------+
|
+------------------------------+
|
+------------------------------+
|
+------------------------------+
...
Each line represents 4 bytes (from lowest address (left) to highest address (right)).

Ответы [ 2 ]

4 голосов
/ 15 октября 2011

Я не собираюсь делать все это для вас, но вот подробное объяснение того, как выполнить то, что происходит.

При входе в main стек выглядит так:

    : (whatever)                        :
    +-----------------------------------+
    | return address (in main's caller) | <- %esp
    +-----------------------------------+

Стандартный код пролога:

pushl   %ebp
movl    %esp, %ebp

    : (whatever)                        :
    +-----------------------------------+
    | return address (in main's caller) |
    +-----------------------------------+
    | saved %ebp                        | <- new %ebp = %esp
    +-----------------------------------+

Это выравнивает стек до 16-байтовой границы, обнуляя младшие 4 бита из %esp:

andl    $-16, %esp

    : (whatever)                        :
    +-----------------------------------+
    | return address (in main's caller) |
    +-----------------------------------+
    | saved %ebp                        | <- new %ebp
    +-----------------------------------+
    : some unknown amount of space      :
    : (0, 4, 8 or 12 bytes)             : <- %esp
    +-----------------------------------+

... куда вы попали. Продолжение:

Это вычитает 16 байтов из указателя стека, что создает 16 байтов зарезервированного пространства для использования main:

subl    $16, %esp

    : (whatever)                        :
    +-----------------------------------+
    | return address (in main's caller) |
    +-----------------------------------+
    | saved %ebp                        | <- %ebp
    +-----------------------------------+
    : some unknown amount of space      :
    : (0, 4, 8 or 12 bytes)             :
    +-----------------------------------+
    | 16 bytes of reserved  space       |
    |                                   |
    |                                   |
    |                                   | <- %esp
    +-----------------------------------+

Сейчас main звонит firstCall; инструкция call возвращает адрес возврата, поэтому в точке сразу после ввода firstCall стек будет выглядеть следующим образом:

call    firstCall

    : (whatever)                        :
    +-----------------------------------+
    | return address (in main's caller) |
    +-----------------------------------+
    | saved %ebp                        | <- %ebp
    +-----------------------------------+
    : some unknown amount of space      :
    : (0, 4, 8 or 12 bytes)             :
    +-----------------------------------+
    | 16 bytes of reserved space        |
    |                                   |
    |                                   |
    |                                   |
    +-----------------------------------+
    | return address (in main)          | <- %esp
    +-----------------------------------+

При возврате к main обратный адрес будет снова удален из-за инструкции ret в конце firstCall.

... и так далее. Просто продолжайте трассировать код таким же образом, следуя действиям %esp.

Другая вещь, которая, возможно, нуждается в объяснении, - это leave, который появляется в код эпилога различных подпрограмм. Вот как это работает для main:

Непосредственно перед leave ближе к концу main стек выглядит следующим образом (мы вернулись с firstCall и сохранил значение в зарезервированном пространстве):

    : (whatever)                        :
    +-----------------------------------+
    | return address (to main's caller) |
    +-----------------------------------+
    | saved %ebp                        | <- %ebp
    +-----------------------------------+
    : some unknown amount of space      :
    : (0, 4, 8 or 12 bytes)             :
    +-----------------------------------+
    | %eax returned by firstCall        |
    | (and 12 bytes that were never     |
    |  used)                            |
    |                                   | <- %esp
    +-----------------------------------+

leave эквивалентно movl %ebp, %esp, за которым следует popl %ebp. Итак:

movl   %ebp, %esp   ; (first part of "leave")

    : (whatever)                        :
    +-----------------------------------+
    | return address (in main's caller) |
    +-----------------------------------+
    | saved %ebp                        | <- %esp = current %ebp
    +-----------------------------------+ 
    : some unknown amount of space      :  }
    : (0, 4, 8 or 12 bytes)             :  }
    +-----------------------------------+  } all of this stuff is
    | %eax returned by firstCall        |  }  irrelevant now
    | (and 12 bytes that were never     |  }
    |  used)                            |  }
    |                                   |  }
    +-----------------------------------+

popl   %ebp         ; (second part of "leave")

    : (whatever)                        :
    +-----------------------------------+
    | return address (in main's caller) | <- %esp  (%ebp has now been restored to the
    +-----------------------------------+            value it had on entry to "main")
      (and now-irrelevant stuff below)           

И, наконец, ret выскакивает адрес возврата, и выполнение продолжается внутри как называется main.

2 голосов
/ 12 октября 2011

Разрыв в строке return count в gdb, а затем просто напечатайте стопку, используя что-то вроде x/30xw $esp.Вы можете прерваться раньше и заметить $esp перед входом в ту часть стека, в которую вы хотите войти, чтобы получить более точное количество, чем мое дикое предположение о 30 словах.

...