2012-05-05 3 views
2

Я недавно пытался реализовать динамические функции на C++ с использованием шестнадцатеричных эквивалентов буфера и RAW разных операторов сборки. Для иллюстрации простого прыжка:Встроенная сборка - cdecl и подготовка стека

byte * buffer = new buffer[5]; 
*buffer = '0xE9'; // Hex for jump 
*(uint*)(buffer + 1) = 'address destination'; 

Я не испытывал в сборке, но я знаю достаточно, чтобы создать самые простые функции. Сейчас я создаю функции cdecl в необработанной памяти. Проблема в том, что я не знаю, сколько я хочу нажать на стек (для памяти) с sub. Давайте рассмотрим эту функцию в качестве примера:

int MyTest(int x, int y) { return x + y; } 

long TheTest(int x, int y) 
{ 
    return MyTest(x, 5); 
} 

08048a20 <_Z6TheTestii>: 
_Z6TheTestii(): 
8048a20: 55      push %ebp 
8048a21: 89 e5     mov %esp,%ebp 
8048a23: 83 ec 18    sub $0x18,%esp 
8048a26: c7 44 24 04 05 00 00 movl $0x5,0x4(%esp) 
8048a2d: 00 
8048a2e: 8b 45 08    mov 0x8(%ebp),%eax 
8048a31: 89 04 24    mov %eax,(%esp) 
8048a34: e8 c2 ff ff ff   call 80489fb <_Z6MyTestii> 
8048a39: c9      leave 
8048a3a: c3      ret  

Как вы можете видеть, первый является C++ код и ниже является ASM из функции «TheTest». Можно сразу заметить, что стек вытолкнут на 24 (0x18) байта (как уже упоминалось ранее, я не испытываю использования сборки, поэтому я не могу использовать правильные термины и/или быть полностью прав). Для меня это не имеет никакого смысла. Почему требуется 24 байта, когда используются только 2 разных целых числа? Используется переменная «x», которая равна 4 байтам, и значение «5», которое также использует 4 байта (помните, что это cdecl, поэтому вызывающая функция берет память о функции аргументов) не составляет 24. ...

Теперь вот дополнительный пример, который делает меня действительно задуматься о выходе сборки:

int NewTest(int x, char val) { return x + val; } 

long TheTest(int x, int y) 
{ 
    return NewTest(x, (char)6); 
} 

08048a3d <_Z6TheTestiiii>: 
_Z6TheTestiiii(): 
8048a3d: 55      push %ebp 
8048a3e: 89 e5     mov %esp,%ebp 
8048a40: 83 ec 08    sub $0x8,%esp 
8048a43: c7 44 24 04 06 00 00 movl $0x6,0x4(%esp) 
8048a4a: 00 
8048a4b: 8b 45 08    mov 0x8(%ebp),%eax 
8048a4e: 89 04 24    mov %eax,(%esp) 
8048a51: e8 ca ff ff ff   call 8048a20 <_Z7NewTestic> 
8048a56: c9      leave 
8048a57: c3      ret  

Единственное различие здесь (за исключением значений) является тот факт, что я использую «полукокса '(1 байт) вместо целого. Если мы посмотрим на код сборки, это заставит указатель стека только на 8 байтов. Это разница в байт из предыдущего примера. Как человек из C++ и вне его, не знаю, что происходит. Я был бы очень признателен, если бы кто-нибудь мог просветить меня по этому вопросу!

ПРИМЕЧАНИЕ: Причина, по которой я размещаю здесь вместо чтения книги ASM, заключается в том, что мне нужно использовать сборку для этой одной функции. Так что я не хочу, чтобы прочитать целую книгу для 40 строк кода ...

EDIT: Я также не заботиться о платформе-зависимости, я только заботу о Linux 32bit :)

+0

Почему вы не используете libffi? –

+0

@DavidHeffernan, это не весело :) –

+0

@Elliott Почему вы не используете отладчик и не видите, как выглядит стек стека внутри 'TheTest', и посмотрите, для чего используется дополнительное пространство? –

ответ

2

стек фрейм, созданный в TheTest имеет как локальные (автоматический) переменные и аргументы функции, такие как MyTest и NewTest, вызываемые TheTest. Рамка выдвигается и выталкивается TheTest, поэтому, если она достаточно велика, чтобы удерживать аргументы функций, которые она вызывает, размер не имеет большого значения.

Вывод компилятора, который вы видите, является результатом нескольких проходов компилятора. Каждый проход может выполнять преобразования и оптимизации, которые уменьшают требуемый размер кадра; Я подозреваю, что в каком-то раннем состоянии компилятор нуждался в 24 байтах кадра и никогда не уменьшал его, даже несмотря на то, что код был оптимизирован.

ABI компилятора на вашей платформе определит некоторые правила выравнивания стека, которые вы должны соблюдать, поэтому размеры кадра округлены, чтобы соответствовать этим требованиям.

Эти функции используют указатель кадра %ebp%, хотя это не выигрыш в размере или производительности кода; это может помочь отладки.

+1

Какое интересное объяснение! Да, я собрал без какой-либо оптимизации, чтобы сделать ASM но насколько верно ваше утверждение о том, что до тех пор, пока оно достаточно велико, чтобы содержать аргументы функций, которые он вызывает, размер не имеет большого значения. Дело в том, что у меня есть вектор, который включает структуру. структура определяет разные типы (их размер и если они являются указателем/ссылкой). Могу ли я просто рассчитать размер всех аргументов, а затем нажать стек (а также выровнять его с мощностью 2)? Для грязного примера: http: //pastebin.com/mV4bHkVr –

+1

Да, но вы, вероятно, должны округлить до кратного 8: sizeToPush = (sizeToPush + 7) & -8; –

+1

Вау, это был некоторый массивный бит кода. Обычно я понимаю фрагменты кода, поэтому это было немного неловко но я могу рассчитывать на этот фрагмент кода, чтобы выровнять мой код с кратным 8? Не хотите, чтобы в итоге было снесено программное обеспечение :) EDIT: и он работает с unsigned chars я его принимаю? –

0

Там это некоторый код пролога и эпилога, который вставляется в эти функции. Попробуйте записать сборку обнаженных функций, т.е.

__declspec(naked) void UsernameIdTramp() // 10 byter, 5 bytes saves + 5 bytes for tramp 
{ 
    __asm 
    { 
     nop; nop; nop; nop; nop; // 5 bytes copied from target - 
     nop; nop; nop; nop; nop; // 5 bytes for the jump back. 
    } 
} 
1

Мне кажется, что ваш компилятор совершает ошибку для первой функции (возможно, отсутствует оптимизация использования стека). Также странно, что ваш компилятор использует две инструкции (с переходом на предварительно выделенный слот стека), а не одну команду push.

Вы компилируете без оптимизации? Не могли бы вы разместить свою командную строку компилятора?

+0

Да Я компилирую без какой-либо оптимизации. Возможно, это был глупый шаг? Поскольку я новичок в сборке, я подумал, что было бы целесообразно компилировать без какой-либо оптимизации, чтобы код оставался простым ... –

1

Это делается для того, чтобы стеки выровнялись до нескольких 32 байтов, чтобы команды SIMD могли использоваться с переменными в стеке.

Смежные вопросы