Переменная доступа GAS ASM PIE x86-64 с инструкцией LEA - PullRequest
0 голосов
/ 15 сентября 2018

Я пытаюсь создать программу сборки с синтаксисом GAS, которая может обращаться к ее переменным из секции .data независимо от положения в арке x86-64 с применением 32bit arch и IS (%eip вместо %rip).

Независимо от того, какие регистры я пробовал, лучший результат, который я получил, был Segmentation fault: 11, и даже это для доступа к EIP, который я не должен был делать вообще, поэтому SF. Лучший результат, потому что это, по крайней мере, сказало мне что-то иное, чем «я не подойду».

Я компилирую файл с gcc в macOS 10.13.6 середина 2010 года Intel Core 2 Duo (поэтому clang, вероятно):

$ gcc --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 9.1.0 (clang-902.0.39.2)
Target: x86_64-apple-darwin17.7.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

и передачи компоновщику некоторых параметров с помощью:

gcc -m32 -Wl,-fatal_warnings,-arch_errors_fatal,-warn_commons,-pie test.s

ld: предупреждение: пирог отключен. Абсолютная адресация (возможно, -mdynamic-no-pic) не разрешена в коде, подписанном PIE, но используется в _main из /whwhat.../test-a07cf9.o. Чтобы исправить это предупреждение, не компилируйте с -mdynamic-no-pic и не связывайте с -Wl, -no_pie ld: фатальное предупреждение (я) вызвало ошибку (-fatal_warnings) clang: error: сбой команды компоновщика с кодом выхода 1 (используйте -v для просмотра вызова) 1


test.s

.text
.global _main

_main:
    xor %eax, %eax
    xor %ebx, %ebx

    # lea var1(%esi/edi/ebp/esp), %ebx  # can't compile, not PIE
    # lea var1(%eip), %ebx  # segfault, obvs

    # lea (%esp), %ebx      # EBX = 17
    # lea (%non-esp), %ebx  # segfault

    # lea 0(%esi), %ebx     # segfault 
    # lea 0(%edi), %ebx     # segfault
    # lea 0(%ebp), %ebx     # EBX = 0
    # lea 0(%esp), %ebx     # EBX = 17
    # lea 0(%eip), %ebx     # segfault, obvs

    movl (%ebx), %eax
    ret

.data
    var1: .long 6

.end

Я запускаю его с ./a.out; echo $?, чтобы проверить значение EAX от ret в конце.

Я посмотрел на различные источники, но в основном это синтаксис Intel или один из этих вопросов - 1 , 2 , 3 . Я попытался разобрать простейший пример C, который мог придумать, то есть глобальную переменную + return из main() - gcc -S test.c -fPIE -pie -fpie -m32:

int var1 = 6;
int main() { return var1; }

, что в основном привело к:

    .section    __TEXT,__text,regular,pure_instructions
    .macosx_version_min 10, 13
    .globl  _main                   ## -- Begin function main
    .p2align    4, 0x90
_main:                                  ## @main
    .cfi_startproc
## BB#0:
    pushl   %ebp
Lcfi0:
    .cfi_def_cfa_offset 8
Lcfi1:
    .cfi_offset %ebp, -8
    movl    %esp, %ebp
Lcfi2:
    .cfi_def_cfa_register %ebp
    pushl   %eax
    calll   L0$pb
L0$pb:
    popl    %eax
    movl    $0, -4(%ebp)
    movl    _var1-L0$pb(%eax), %eax
    addl    $4, %esp
    popl    %ebp
    retl
    .cfi_endproc
                                        ## -- End function
    .section    __DATA,__data
    .globl  _var1                   ## @var1
    .p2align    2
_var1:
    .long   6                       ## 0x6


.subsections_via_symbols

Это, очевидно, использует MOV в качестве LEA и почти такую ​​же инструкцию, что и моя, за исключением -L0$pb части, которая должна быть +/- подобна адресу _var1 - адресу L0$pb, чтобы попасть в раздел .data.

И все же, когда я пытаюсь использовать тот же подход с метками var1 и _main, ничего:

.text
.global _main

_main:
    xor %eax, %eax
    xor %ebx, %ebx

    #movl var1-_main(%ebp), %eax  # EAX = 191
    #movl var1-_main(%esp), %eax  # EAX = 204
    #movl var1-_main(%eax), %eax  # segfault
    ret

.data
    var1: .long 6

.end

Есть идеи, что я делаю не так?

Edit:

Мне удалось вырезать ненужные вещи из дизассемблированного примера C и в итоге получилось:

.text
.global _main

_main:
    pushl %ebp
    pushl %eax
    calll test

test:
    popl %eax

    /* var1, var2, ... */
    movl var1-test(%eax), %eax

    addl $4, %esp
    popl %ebp
    retl

/**
 * how var1(label) - test(label) skips this label
 * if it's about address subtracting?
 */
blobbbb:
    xor %edx, %edx

.data
var1: .long 6
var2: .long 135

И для меня это не имеет особого смысла, потому что согласно этому руководству вызывающий должен 1) поместить параметры в стек (нет) 2) call метка и callee должны фактически играть с ESP, EBP и другими регистрами. Кроме того, зачем мне нужен промежуточный ярлык или лучше сказать, есть ли способ без него?

1 Ответ

0 голосов
/ 15 сентября 2018

В 32-битных режимах режим относительной адресации eip отсутствует, как в 64-битном режиме. Таким образом, код типа

mov var(%eip), %eax

на самом деле недопустимо и не собирается в 32-битном режиме. (В 64-битной версии адрес будет сокращен до 32-х бит). В традиционных 32-битных бинарных файлах без PIE вы просто сделали бы

mov var, %eax

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

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

  1. узнать абсолютный адрес какой-либо метки и загрузить в нее регистр
  2. добавить к этому расстояние от этого ярлыка до var
  3. доступ к переменной

Шаги 2 и 3 можно комбинировать, используя режим адресации со смещением. Шаг 1 сложный. Есть только одна полезная инструкция, которая сообщает нам, какой адрес местоположения, чей адрес мы не знаем, и это call: инструкция call помещает адрес следующей инструкции в стек и затем переходит к указанному адрес. Если мы скажем call просто перейти к следующему адресу, мы снизим его функциональность до того, что по сути push %eip:

        call Label                  # like push %eip
Label:  ...

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

Итак, из этого мы знаем адрес Label. Затем мы можем извлечь его из стека и использовать его для поиска адреса var:

        call Label
Label:  pop %eax                    # eax = Label
        add $var-Label, %eax        # eax = Label + var - Label = var

и затем мы можем разыменовать это, чтобы получить содержимое var:

        call Label
Label:  pop %eax
        add %eax, $var-Label
        mov (%eax), %eax            # eax = *var

В реальном коде вы должны объединить дополнение и операнд памяти, чтобы сохранить инструкцию:

        call Label
Label:  pop %eax
        mov var-Label(%eax), %eax   # eax = *var

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

        call Label
Label:  pop %eax
        mov foo-Label(%eax), %ebx   # ebx = *foo
        mov bar-Label(%eax), %ecx   # ecx = *bar

Обратите внимание, что gcc предпочитает вариант этой идиомы для получения содержимого указателя инструкции. Создает кучу таких функций:

___x86.get_pc_thunk.bx:
        mov (%esp), %ebx
        ret

, которые перемещают адрес возврата в указанный регистр. Это специальная функция, которая не следует обычному соглашению о вызовах, она существует для каждого из eax, ebx, ecx, edx, esi и edi, в зависимости от того, какой регистр хочет gcc использовать. Код выглядит так:

        call ___x86.get_pc_thunk.bx # ebx = Label
Label:  mov foo-Label(%ebx), %eax   # eax = *foo
        mov bar-Label(%ebx), %ecx   # ecx = *bar

gcc использует этот код для повышения производительности на процессорах, прогнозирование возврата которых не учитывает эту идиому поддельного вызова. Я не знаю, на какие процессоры это действительно влияет.

Наконец, обратите внимание, что ни одна этикетка не пропускается. Я не совсем понимаю, что вы имеете в виду под blobbbb. Какой элемент управления должен достичь этого ярлыка?

Наконец, ваш пример должен выглядеть так:

        .text
        .global _main

_main:  call Label                  # push %eip
Label:  pop %eax                    # eax = Label
        mov var1-Label(%eax), %eax  # eax = *(Label+var1-Label)
        ret


        .data
var1:   .long 6

Обратите внимание, что директива .end никогда не нужна. Метки, начинающиеся с заглавной буквы L, являются локальными метками, которые не заканчиваются в таблице символов, поэтому компилятор C любит их использовать.

...