Почему mov %%rsp, %%rbp вызывает ошибку сегментации?

Я новичок в встроенной сборке. У меня есть следующая функция C со встроенной сборкой. Я просто пытаюсь проверить, правильно ли работают push %%rbp и mov %%rsp, %%rbp. Моя функция следующая,

test_inlineAssemblyFunction(){

    u64 base, rsp, base1, rsp1;
    asm volatile(

            "mov %%rbp, %0 \n"
            "mov %%rsp, %1 \n"

            "push %%rbp \n"
            "mov %%rbp, %2 \n"

            "mov %%rsp, %%rbp \n" <--- this line is causing the problem
            "mov %%rbp, %3 \n"
            :"=r"(base), "=r"(rsp),"=r"(base1), "=r"(rsp1)
            :
            : "rax","rbx","rsp"
            );
    printf("Before: Base register %x\n", base);
    printf("Before: stack pointer register %x\n", rsp);
    printf("After: (Should be same as previous )Base register %x\n", base1);
    printf("After: (Actually %rsp-8)Base register %x\n", rsp1);

    
}

выход:

Before: Base register ee050e50
Before: stack pointer register ee050e20
After: (Should be same as previous )Base register ee050e50
After: (Actually %rsp-8)Base register ee050e18
Segmentation fault (core dumped)

Из вывода я вижу, что все операторы печати печатают желаемый результат. Но тогда ошибка сегментации. Если я правильно понимаю, Segmentation Fault возникает при попытке чтения или записи в недопустимую ячейку памяти. Итак, в моем случае "mov %%rsp, %%rbp \n" вызывает ошибку сегментации, поэтому мне не разрешено читать rsp из пользовательского пространства?

Обновление: поскольку @Kuba не забыл, что предложила Моника, ниже приводится разборка функции test_inlineAssemblyFunction,

(gdb) disas test_inlineAssemblyFunction
Dump of assembler code for function test_inlineAssemblyFunction:
0x0000000000007366 <+0>:    push   %rbp
0x0000000000007367 <+1>:    mov    %rsp,%rbp
0x000000000000736a <+4>:    push   %rbx
0x000000000000736b <+5>:    sub    $0x28,%rsp
0x000000000000736f <+9>:    mov    %rbp,%rdi
0x0000000000007372 <+12>:   mov    %rsp,%rsi
0x0000000000007375 <+15>:   push   %rbp
0x0000000000007376 <+16>:   mov    %rbp,%rcx
0x0000000000007379 <+19>:   mov    %rsp,%rbp
0x000000000000737c <+22>:   mov    %rax,%rdx
0x000000000000737f <+25>:   mov    %rdi,-0x30(%rbp)
0x0000000000007383 <+29>:   mov    %rsi,-0x28(%rbp)
0x0000000000007387 <+33>:   mov    %rcx,-0x20(%rbp)
0x000000000000738b <+37>:   mov    %rdx,-0x18(%rbp)
0x000000000000738f <+41>:   mov    -0x30(%rbp),%rax
0x0000000000007393 <+45>:   mov    %rax,%rsi
0x0000000000007396 <+48>:   lea    0x275ab(%rip),%rdi        # 0x2e948
0x000000000000739d <+55>:   mov    $0x0,%eax
0x00000000000073a2 <+60>:   callq  0x5570 <printf@plt>
0x00000000000073a7 <+65>:   mov    -0x28(%rbp),%rax
0x00000000000073ab <+69>:   mov    %rax,%rsi
0x00000000000073ae <+72>:   lea    0x275b3(%rip),%rdi        # 0x2e968
0x00000000000073b5 <+79>:   mov    $0x0,%eax
0x00000000000073ba <+84>:   callq  0x5570 <printf@plt>
0x00000000000073bf <+89>:   mov    -0x20(%rbp),%rax
0x00000000000073c3 <+93>:   mov    %rax,%rsi
0x00000000000073c6 <+96>:   lea    0x275c3(%rip),%rdi        # 0x2e990
0x00000000000073cd <+103>:  mov    $0x0,%eax
0x00000000000073d2 <+108>:  callq  0x5570 <printf@plt>
0x00000000000073d7 <+113>:  mov    -0x18(%rbp),%rax
0x00000000000073db <+117>:  mov    %rax,%rsi
0x00000000000073de <+120>:  lea    0x275e3(%rip),%rdi        # 0x2e9c8
0x00000000000073e5 <+127>:  mov    $0x0,%eax
0x00000000000073ea <+132>:  callq  0x5570 <printf@plt>
0x00000000000073ef <+137>:  nop
0x00000000000073f0 <+138>:  mov    -0x8(%rbp),%rbx
0x00000000000073f4 <+142>:  leaveq 
0x00000000000073f5 <+143>:  retq   
End of assembler dump.
(gdb)

person user45698746    schedule 13.01.2021    source источник
comment
Пробовали ли вы смотреть на вывод asm компилятора, чтобы увидеть, как ваш asm смешивается с выводом компилятора? В зависимости от -fomit-frame-pointer или нет, операнды "=m" будут ссылаться относительно RSP или RBP, поэтому возиться с RSP и RBP в ассемблерном операторе с операндами памяти кажется ужасной идеей. (Кроме того, я не думаю, что очиститель RSP действительно работает). Кроме того, inline-asm небезопасно наступать на красную зону ниже RSP, но на практике это безопасно в нелистовых функциях, таких как: gcc и clang не будут использовать res-зону, потому что вы вызываете printf.   -  person Peter Cordes    schedule 13.01.2021
comment
Чтение rsp — это хорошо, но изменение rbp во встроенной сборке без затирания — нет. Также небезопасно помещать в стек встроенный ассемблер x86-64 из-за красной зоны. И, конечно же, небезопасно оставлять что-то в стеке, так что указатель стека находится в другом положении, чем то, где он был оставлен (удаление rsp не помогает).   -  person Nate Eldredge    schedule 13.01.2021
comment
@PeterCordes Извините. Я исправляю свой код. Я пробовал и с =r, и с =m. =m вызывает сбой стека. и =r вызывает ошибку сегментации   -  person user45698746    schedule 13.01.2021
comment
Вы изменяете RSP без восстановления, поэтому, когда функция пытается вернуться, вероятно, RSP не указывает на адрес возврата. Клоббер "rsp" не делает это безопасным; Я почти уверен, что он поддерживается. Опять же, посмотрите на весь ассемблерный вывод компилятора, например. Как удалить шум из вывода сборки GCC/clang?   -  person Peter Cordes    schedule 13.01.2021
comment
@NateEldredge, не могли бы вы привести пример изменения rbp с помощью clobber?   -  person user45698746    schedule 14.01.2021


Ответы (2)


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

Когда вы пишете на ассемблере, вы взаимодействуете со всем окружающим ассемблерным кодом, поэтому я сбит с толку, почему вы делаете это в темноте. Первое, что вам нужно сделать, это остановиться и выяснить, как получить выходные данные сборки от компилятора, то есть как передать параметры, которые компилятор ожидает сохранить такие выходные данные сборки в файл, в дополнение к сохранению объектного кода в файл. Затем перестройте свою программу и посмотрите, как ваша сборка сочетается с остальными частями. Подсказка: это не так :)

Если на данный момент выяснение этого в вашей собственной системе слишком отвлекает, вы можете использовать https://godbolt.org для написания кода выберите один из компиляторов gcc, а затем вы сможете изучить выходные данные сборки и запустить программу в облаке и наблюдать за результатами. Все, что вам нужно для этого, это веб-браузер. Больше ничего локально устанавливать не нужно.

person Kuba hasn't forgotten Monica    schedule 13.01.2021
comment
Я добавил ассемблерный код, сгенерированный компилятором. Как предположил @Nate Eldredge, проблема, с которой я столкнулся, связана с изменением rbp. Я попытался заменить rbp на rax. функция работает без ошибок. - person user45698746; 14.01.2021
comment
@ user45698746 Вы не изменяете rbp волей-неволей. Это не так, как если бы вы посмотрели на доступные регистры, бросили дротик и придумали rbp и можете вместо этого использовать что-то другое. Вы пытаетесь имитировать код, который видели раньше. За исключением того, что вы получили только первую половину этого кода: то, что вы показываете, будет отлично работать, если вы добавите отсутствующую вторую половину. То, что вы написали, обычно используется в качестве пролога функции, которая устанавливает кадр стека. Каждому прологу нужен эпилог: обратная операция перед возвратом из функции. Каркас стека, который вы построили, должен быть снесен. Тогда получится :) - person Kuba hasn't forgotten Monica; 14.01.2021
comment
@Kubahasn'forgottenMonica: создание другого кадра стека внутри функции, отличной от naked, будет работать только в том случае, если ни один из ваших входных или выходных операндов не является "m" или "=m". В противном случае GCC заполнит ваш шаблон %0, например, -4(%rbp) или 12(%rsp), в зависимости от -fomit-frame-pointer или нет. Но да, если вы избегаете каких-либо операндов "m" или "g", и функция не использует красную зону (например, потому что это не конечная функция), то вы можете просто безопасно использовать стек внутри вашего шаблона asm, как вы говорите как пока вы вернете все обратно до конца инструкции asm. - person Peter Cordes; 14.01.2021
comment
(Я упоминаю операнды памяти, потому что они изначально были в вопросе.) - person Peter Cordes; 14.01.2021

Проблема не в том, что вы читаете rsp. Дело в том, что вы затерли rbp (перезаписав его значением, скопированным из rsp) и rsp (выполнив push без совпадения pop) из ассемблерного блока, не сообщив компилятору о затираниях. Первое можно сделать, добавив rbp в список затирания; последнее вообще не законно.

person R.. GitHub STOP HELPING ICE    schedule 14.01.2021