Разработайте функцию на C из кода сборки - PullRequest
0 голосов
/ 11 ноября 2019

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

Это разобранный код функции.

(Hand transcribed from an image, typos are possible
 especially in the machine-code.  See revision history for the image)
0000000000000000 <ex3>:
   0:   b9 00 00 00 00       mov    0x0,%ecx
   5:   eb 1b                jmp    L2  // 22 <ex3+0x22>
   7:   48 63 c1     L1:     movslq %ecx,%rax
   a:   4c 8d 04 07          lea    (%rdi,%rax,1),%r8
   e:   45 0f b6 08          movzbl (%r8),%r9d
  12:   48 01 f0             add    %rsi,%rax
  15:   44 0f b6 10          movzbl (%rax),%r10d
  19:   45 88 10             mov    %r10b,(%r8)
  1c:   44 88 08             mov    %r9b,(%rax)
  1f:   83 c1 01             add    $0x1,%ecx
  22:   39 d1        L2:     cmp    %edx,%ecx
  24:   7c e1                jl     L1   // 7 <ex3+0x7>
  26:   f3 c3                repz retq

Мой код ( подпись функции не задана или не установлена ​​):

#include <assert.h>

int
ex3(int rdi, int rsi,int edx, int r8,int r9 ) {
    int ecx = 0;
    int rax;
    if(ecx>edx){
         rax = ecx;
        r8 =rdi+rax;
        r9 =r8;
        rax =rsi;
        int r10=rax;
        r8=r10;
        rax =r9;
        ecx+=1;
    }
    return rax;
}

Пожалуйста, объясните, в чем причина ошибок, если вы их узнаете.

Ответы [ 3 ]

1 голос
/ 11 ноября 2019

Я почти уверен, что это так: поменяйте местами две области памяти:

void memswap(unsigned char *rdi, unsigned char *rsi, int edx) {
        int ecx;
        for (ecx = 0; ecx < edx; ecx++) {
                unsigned char r9 = rdi[ecx];
                unsigned char r10 = rsi[ecx];
                rdi[ecx] = r10;
                rsi[ecx] = r9;
        }
}
1 голос
/ 11 ноября 2019

(Примечание редактора: это частичный ответ, который только относится к структуре цикла. Он не покрывает байтовые нагрузки movzbl или тот факт, что некоторые из этих переменных являются указателями или типомширина. Есть место для других ответов, чтобы охватить другие части вопроса.)


C поддерживает goto, и хотя их использование часто осуждается, они очень полезны здесь. Используйте их, чтобы сделать его максимально похожим на сборку. Это позволяет вам убедиться в том, что код работает, прежде чем вы начнете внедрять более правильные механизмы управления потоком, такие как циклы while. Поэтому я бы сделал что-то вроде этого:

    goto L2;
L1:
    rax = ecx;
    r8 =rdi+rax;
    r9 =r8;
    rax =rsi;
    int r10=rax;
    r8=r10;
    rax =r9;
    ecx+=1;
L2:
    if(edx<ecx) 
        goto L1;

Вы можете легко преобразовать приведенный выше код в:

while(edx<ecx) {
    rax = ecx;
    r8 =rdi+rax;
    r9 =r8;
    rax =rsi;
    int r10=rax;
    r8=r10;
    rax =r9;
    ecx+=1;
}

Обратите внимание, что я не проверял, есть ли код в блоке L1 итогда позже блок while будет правильным или нет. (Примечание редактора: он пропускает все обращения к памяти). Но ваш прыжок был неправильным и теперь исправлен.

Что вы можете сделать отсюда (опять же, если предположить, что это правильно), это начать пытаться увидеть шаблоны. Кажется, что ecx используется как некоторая переменная индекса. И переменная rax может быть заменена в начале. Мы можем сделать несколько других подобных изменений. Это дает нам:

int i=0;
while(edx<i) {
                    // rax = ecx;
                    // r8 =rdi+i; // r8=rdi+i
                    // r9 = rdi + i; // r9 = r8
                    // rax =rsi;
    int r10 = rsi;  // int r10=rax;
    r8 = r10;
    rax = r9 = rdi+i;
    i++;
}

Здесь ясно, что что-то немного ненадежно. Условие while равно edx<i, но i равно с шагом и не уменьшается при каждой итерации. Это хороший признак того, что что-то не так. Я не достаточно опытен в сборке, чтобы понять это, но по крайней мере это метод, который вы можете использовать. Просто сделайте это шаг за шагом.

add $0x1,%ecx - это синтаксис AT & T для увеличения ecx на 1. Согласно этому сайту с использованием синтаксиса Intel, результат сохраняется в первый операнд. В синтаксисе AT & T это последний операнд.

Одна интересная вещь, на которую следует обратить внимание: если мы удалим оператор goto L2, это будет эквивалентно

do {
    // Your code
} while(edx<ecx);

Цикл while может быть скомпилирован в цикл do-while с дополнительным переходом. (См. Почему циклы всегда компилируются в стиле "do ... while" (прыжок с хвоста)? ). Это довольно легко понять.

В сборке циклы создаются с помощью gotos, которые прыгают назад в коде. Вы проверяете, а затем решаете, хотите ли вы вернуться назад. Таким образом, чтобы выполнить тестирование до первой итерации, сначала нужно перейти к тесту. (Компиляторы также иногда компилируют циклы while с if()break вверху и jmp внизу. Но только с отключенной оптимизацией. См. While, Do While, Для циклов на языке ассемблера (emu8086) )

Переход вперед часто является результатом компиляции операторов if.

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

0 голосов
/ 11 ноября 2019

Для тех, кто предпочитает формат .S для GCC, я использовал:

ex3:
  mov $0x0, %ecx
  jmp lpe
  lps:
      movslq %ecx, %rax
      lea (%rdi, %rax, 1), %r8
      movzbl (%r8), %r9d
      add %rsi, %rax
      movzbl (%rax), %r10d
      mov %r10b, (%r8)
      mov %r9b, (%rax)
      add $0x1, %ecx
  lpe:
  cmp %edx, %ecx
  jl lps
repz retq


.data
.text
.global _main
_main:
    mov $0x111111111111, %rdi
    mov $0x222222222222, %rsi
    mov $0x5, %rdx
    mov $0x333333333333, %r8
    mov $0x444444444444, %r9
    call ex3
    xor %eax, %eax
    ret

, затем вы можете скомпилировать его с помощью gcc main.S -o main и запустить objdump -x86-asm-syntax=intel -d main, чтобы увидеть его в формате Intel ИЛИ запуститьрезультирующий main исполняемый файл в декомпиляторе ... но ме ... давайте сделаем некоторую ручную работу ..

Сначала я бы преобразовал синтаксис AT & T в более известный синтаксис Intel .. так:

ex3:
  mov ecx, 0
  jmp lpe
  lps:
      movsxd rax, ecx
      lea r8, [rdi + rax]
      movzx r9d, byte ptr [r8]
      add rax, rsi
      movzx r10d, byte ptr [rax]
      mov byte ptr [r8], r10b
      mov byte ptr [rax], r9b
      add ecx, 0x1
  lpe:
  cmp ecx, edx
  jl lps
rep ret

Теперь я ясно вижу, что от lps (начало цикла) до lpe (конец цикла) является циклом for.

Как? Потому что сначала он устанавливает регистр счетчика (ecx) в 0. Затем он проверяет, является ли ecx < edx, выполняя cmp ecx, edx, за которым следует jl (переход, если меньше). Если это так, он запускает коди увеличивает ecx на 1 (add ecx, 1) .. если нет, то существует блок.

Таким образом, он выглядит следующим образом: for (int32_t ecx = 0; ecx < edx; ++ecx) .. (обратите внимание, что edx - младшие 32-битныеrdx).

Итак, теперь мы переведем остаток со знанием того, что:

r10 - это 64-битный регистр. r10d - старшие 32 бита, r10b - младшие 8 бит. r9 - это 64-битный регистр. Применяется та же логика, что и r10.

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

typedef union Register
{
    uint64_t reg;
    struct
    {
        uint32_t upper32;
        uint32_t lower32;
    };

    struct
    {
        uint16_t uupper16;
        uint16_t ulower16;
        uint16_t lupper16;
        uint16_t llower16;
    };

    struct
    {
        uint8_t uuupper8;
        uint8_t uulower8;

        uint8_t ulupper8;
        uint8_t ullower8;

        uint8_t luupper8;
        uint8_t lulower8;

        uint8_t llupper8;
        uint8_t lllower8;
    };
} Register;

Какое лучше ... Вы можете выбрать для себя ... Теперь мы можем начатьглядя на сами инструкции .. movsxd или movslq перемещает 32-битный регистр в 64-битный регистр с расширением знака.

Теперь мы можем написать код:

uint8_t* ex3(uint8_t* rdi, uint64_t rsi, int32_t edx)
{
    uintptr_t rax = 0;
    for (int32_t ecx = 0; ecx < edx; ++ecx)
    {
        rax = ecx;
        uint8_t* r8 = rdi + rax;
        Register r9 = { .reg = *r8 }; //zero extend into the upper half of the register
        rax += rsi;

        Register r10 = { .reg = *(uint8_t*)rax }; //zero extend into the upper half of the register
        *r8 = r10.lllower8;
        *(uint8_t*)rax = r9.lllower8;
    }

    return rax;
}

Надеюсь, я ничего не напортачил ..

...