Ошибка сегментации при вызове функции сборки x86 из программы C

Я пишу программу на C, которая вызывает функцию сборки x86, которая складывает два числа. Ниже представлено содержимое моей программы на C (CallAssemblyFromC.c):

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

int addition(int a, int b);

int main(void) {
    int sum = addition(3, 4);
    printf("%d", sum);
    return EXIT_SUCCESS;
}

Ниже приведен код функции Assembly (моя идея состоит в том, чтобы закодировать с нуля пролог и эпилог кадра стека, я добавил комментарии, объясняющие логику моего кода) (add.s):

.text

# Here, we define a function addition
.global addition
addition:
    # Prologue:
    # Push the current EBP (base pointer) to the stack, so that we
    # can reset the EBP to its original state after the function's
    # execution
    push %ebp
    # Move the EBP (base pointer) to the current position of the ESP
    # register
    movl %esp, %ebp

    # Read in the parameters of the addition function
    # addition(a, b)
    #
    # Since we are pushing to the stack, we need to obtain the parameters
    # in reverse order:
    # EBP (return address) | EBP + 4 (return value) | EBP + 8 (b) | EBP + 4 (a)
    #
    # Utilize advanced indexing in order to obtain the parameters, and
    # store them in the CPU's registers
    movzbl 8(%ebp), %ebx
    movzbl 12(%ebp), %ecx

    # Clear the EAX register to store the sum
    xorl %eax, %eax
    # Add the values into the section of memory storing the return value
    addl %ebx, %eax
    addl %ecx, %eax

Я получаю ошибку ошибки сегментации, что кажется странным, учитывая, что я думаю, что выделяю память в соответствии с соглашениями о вызовах x86 (например, выделение правильных разделов памяти параметрам функции). Кроме того, если у кого-то из вас есть решение, мы были бы очень признательны, если бы вы могли дать несколько советов относительно того, как отлаживать программу сборки, встроенную с помощью C (я использовал отладчик GDB, но он просто указывает на строку C программа, в которой происходит ошибка сегментации, вместо строки в программе сборки).

См. также:  Как запустить два файла .jmx вместе в JMeter и создать один отчет для обоих файлов?

Пошаговое выполнение кода сборки: stackoverflow.com/questions/2420813/   —  person Adam Lee    schedule 13.11.2020

movl %ebp, %esp в синтаксисе AT&T перемещает значение из ebp в регистр esp. Вы хотите обратного.   —  person Adam Lee    schedule 13.11.2020

Просто для проверки — вы уверены, что правильно компилируете и собираете всю программу как 32-битный код? В 64-битной системе это обычно означает использование -m32 при компиляции и компоновке.   —  person Adam Lee    schedule 14.11.2020

@NateEldredge Да, я компилирую программу как 32-битный код.   —  person Adam Lee    schedule 14.11.2020

Понравилась статья? Поделиться с друзьями:
IT Шеф
Комментарии: 2
  1. Adam Lee

    Вы портите стек здесь:

    movb %al, 4(%ebp)
    

    Чтобы вернуть значение, просто поместите его в eax. Также зачем вам очищать eax? это неэффективно, так как вы можете загрузить первое значение непосредственно в eax, а затем добавить к нему.

    Также EBX необходимо сохранить, если вы собираетесь его использовать, но в любом случае он вам не нужен.

    Я внес эту правку в свой код, но, к сожалению, все еще получаю ошибку сегментации. person Adam Lee; 14.11.2020

  2. Adam Lee
    1. У вашей функции нет эпилога. Вам нужно восстановить %ebp и вернуть стек обратно на место, а затем ret. Если это действительно отсутствует в вашем коде, то это объясняет ваш segfault: ЦП продолжит выполнение любого мусора, который окажется после завершения вашего кода в памяти.

    2. Вы затираете (т.е. перезаписываете) регистр %ebx, который должен быть сохранен вызываемым пользователем. (Вы упомянули о соблюдении соглашений о вызовах x86, но, похоже, упустили эту деталь.) Это может быть причиной вашего следующего segfault после того, как вы исправите первый. Если вы используете %ebx, вам необходимо сохранить и восстановить его, например с push %ebx после вашего пролога и pop %ebx перед эпилогом. Но в этом случае лучше переписать свой код, чтобы вообще не использовать; увидеть ниже.

    3. movzbl загружает 8-битное значение из памяти и расширяет его с помощью нуля в 32-битный регистр. Здесь параметры int, так что они уже 32-битные, так что простой movl является правильным. В его нынешнем виде ваша функция будет давать неверные результаты для любых аргументов, которые отрицательны или больше 255.

    4. Вы используете ненужное количество регистров. Вы можете переместить первый операнд для добавления непосредственно в %eax, а не помещать его в %ebx и добавлять его к нулю. А на x86 не обязательно записывать оба операнда в регистры перед добавлением; арифметические инструкции имеют форму mem, reg, в которой один операнд может быть загружен прямо из памяти. При таком подходе нам не нужны никакие регистры, кроме самого %eax, и, в частности, нам больше не нужно беспокоиться о %ebx.

    Я бы написал:

    .text
    
    # Here, we define a function addition
    .global addition
    addition:
        # Prologue:
        push %ebp
        movl %esp, %ebp
    
        # load first argument
        movl 8(%ebp), %eax 
        # add second argument
        addl 12(%ebp), %eax
    
        # epilogue
        movl %ebp, %esp  # redundant since we haven't touched esp, but will be needed in more complex functions 
        pop %ebp
        ret
    

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

    .text
    .global addition
    addition:
        movl 4(%esp), %eax
        addl 8(%esp), %eax
        ret
    

    Большое вам спасибо за этот замечательный ответ, который был настолько подробным. Мне было интересно, есть ли у вас какие-либо советы по отладке программ C, которые связаны с кодом сборки (в отладчике GDB я могу видеть только линию ошибки сегмента в программе C вместо программы сборки)? person Adam Lee; 14.11.2020

    Ответы в ссылке, которую опубликовал Jabberwocky, охватывают большую часть того, что есть. Полезные команды: display/i $eip info registers si ni break disassemble print $eax x/8xw $ebp person Adam Lee; 14.11.2020

    У меня был только один уточняющий вопрос по второму пункту, который вы отметили: когда вы говорите, что регистр% ebx должен быть сохранен вызываемым пользователем, что именно это означает? Кроме того, является ли регистр% ebx единственным регистром в x86, который сохраняется для вызываемого абонента? person Adam Lee; 14.11.2020

    Это означает, что ваша функция (та, которая вызывается, то есть вызываемый) должна гарантировать, что ее значение при возврате такое же, как при входе, так что любое значение, которое ваш вызывающий, возможно, сохранял, все еще там, как если бы оно было никогда не менялся. См. stackoverflow.com/questions/ 9268586 /. Сохраненные регистры вызывающего / вызываемого являются частью соглашений о вызовах; если в вашей ссылке это не объясняется, вы можете увидеть wiki.osdev.org/Calling_Conventions. Регистры %ebx, %esi, %edi, %ebp — это регистры, сохраненные вызываемым пользователем на 32-разрядной системе x86. person Adam Lee; 14.11.2020

    Попался. Последний вопрос: всегда ли регистр EAX должен хранить возвращаемое значение функции в Assembly? Я думал, что EBP + 4 хранит возвращаемое значение? person Adam Lee; 14.11.2020

    @AdamLee: Нет, это EAX, по крайней мере, для функций, возвращающих 32-битные целые числа или указатели (другие возвращаемые типы имеют другие соглашения). Я не знаю ни одной ситуации, когда возвращаемое значение было бы конкретно EBP + 4, и не уверен, откуда у вас эта идея. Официальной ссылкой на все это является System V ABI, который вам действительно стоит изучить, если вы собираетесь писать ассемблерные программы для Unix-подобных ОС. person Adam Lee; 14.11.2020

    @AdamLee: также см. stackoverflow.com/tags/x86/info, где есть еще много полезных ссылок. person Adam Lee; 14.11.2020

Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: