Clang 11 и G CC 8 O2 ломает линию сборки - PullRequest
1 голос
/ 10 марта 2020

У меня есть небольшой фрагмент кода с некоторой встроенной сборкой, которая правильно печатает argv [0] в O0, но ничего не печатает в O2 (при использовании Clang. G CC, с другой стороны, печатает строку сохраняется в envp [0] при печати argv [0]). Эта проблема также ограничивается только argv (другие два параметра функции могут использоваться как ожидается с включенной оптимизацией или без нее). Я протестировал это как с G CC, так и с Clang, и эта проблема возникла у обоих компиляторов.

Вот код:

Редактировать: добавлены определения системного вызова.

Edit: добавление rcx и r11 в список clobber для системных вызовов исправило проблему для clang, но g cc из-за ошибки.

Edit: G CC на самом деле не было ошибки, но какая-то странная ошибка в моей системе сборки (CodeLite) привела к тому, что программа запустила какую-то частично собранную программу, даже несмотря на то, что G CC сообщала об ошибках, не распознавая два переданных флага компилятора. Для G CC используйте вместо этого следующие флаги: -fomit-frame-pointer; -fno-асинхронный-раскрутить-таблицы; -O2; -Wall; -nostdinc; -fno-builtin; -nostdlib; -nodefaultlibs; - не стандартная-библиотека; -nostartfiles; -nostdinc ++. Вы также можете использовать эти флаги для Clang, поскольку Clang поддерживает вышеуказанные опции G CC.

1 Ответ

7 голосов
/ 10 марта 2020
  1. Вы не можете использовать расширенный asm в функции naked, только базовый c asm, согласно инструкции g cc . Вам не нужно сообщать компилятору о засоренных регистрах (поскольку он все равно ничего с ними не сделает; в функции naked вы отвечаете за все управление регистрами). И передача адреса entry в расширенном операнде не нужна; просто сделайте jmp entry.

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

  2. Linux x86-64 syscall Системным вызовам разрешено блокировать регистры rcx и r11, поэтому вам необходимо добавить их в списки блокирующих операторов. ваши системные вызовы.

  3. Вы выравниваете стек по 16-байтовой границе, прежде чем перейти к entry. Однако 16-байтовое правило выравнивания основано на предположении, что вы будете вызывать функцию с call, что приведет к добавлению sh дополнительных 8 байтов в стек. Таким образом, вызываемая функция фактически ожидает, что стек изначально будет не кратным 16, а 8 большим или меньшим, чем кратное 16. Таким образом, вы на самом деле неправильно выравниваете стек, и это может быть причиной всех видов ошибок. таинственная проблема.

    Так что либо замените ваш jmp на call, либо вычтите еще 8 байтов из rsp (или просто push какой-нибудь 64-битный регистр на ваш выбор).

  4. Примечание по стилю: unsigned long - это уже 64 бита на Linux x86-64, поэтому было бы более логично c использовать его вместо unsigned long long везде.

  5. Общая подсказка: узнайте об ограничениях регистра в расширенном asm. Вы можете заставить компилятор загружать нужные вам регистры вместо того, чтобы писать инструкции в ассемблере, чтобы сделать это самостоятельно. Таким образом, ваша exit функция может выглядеть следующим образом:

    void exit(unsigned long status) {
        asm volatile("syscall"
            : //no outputs
            :"a"(60), "D" (status)
            :"rcx", "r11");
    }

Это, в частности, экономит вам несколько инструкций, поскольку status уже находится в регистре %rdi при входе в функцию , С вашим исходным кодом компилятор должен переместить его куда-то еще, чтобы вы могли загрузить его в %rdi самостоятельно.

Ваша функция open всегда возвращает 1, что, как правило, не будет фактически открывшимся fd. Поэтому, если ваша программа запускается со стандартным перенаправленным выводом, ваша программа будет записывать в перенаправленный стандартный вывод, а не в tty, как это, кажется, хочет делать. В самом деле, это делает системный вызов open совершенно бессмысленным, потому что вы никогда не используете открываемый файл.

Вы должны организовать open для возврата значения, которое фактически было возвращено системным вызовом, что будет осталось в регистре %rax, когда syscall вернется. Вы можете использовать выходной операнд, чтобы сохранить его во временной переменной (которую компилятор, скорее всего, оптимизирует), и вернуть ее. Вам нужно будет использовать ограничение di git, поскольку оно входит в тот же регистр, что и входной операнд. Я оставляю это как упражнение для вас. Было бы также неплохо, если бы ваша функция write действительно возвращала количество записанных байтов.

...