ASM в C: как разыменовать указатель и добавить смещение? - PullRequest
0 голосов
/ 26 октября 2018

Я чувствую себя немного глупо, но я борюсь с разыменованием указателя (+ добавление смещения) в C. То, что я хочу воссоздать в C, это поведение:

movabs rax, 0xdeadbeef
add rax, 0xa
mov rax, QWORD PTR [rax]

Так в конце rax должно быть: *(0xdeadbeef+0xa) Особенно эквивалентен mov rax, QWORD PTR [rax], так как мне нужно использовать вычисленное значение и извлечь данные (= другой адрес), которые хранятся в этой точке.

Я столько всего перепробовал, но вот мой текущий этап:

void *ptr = (void*)0xdeadbeef;
void *ptr2 = *(void*)(ptr+0xa);

Что означает что-то вроде этого:

   0x7ffff7fe6050:      mov    QWORD PTR [rbp-0x38],rax
   0x7ffff7fe6054:      mov    rax,QWORD PTR [rbp-0x38]
   0x7ffff7fe6058:      add    rax,0xa

РЕДАКТИРОВАТЬ: Это на самом деле не компилируется, я допустил ошибку с предоставленным здесь C-кодом и не могу понять, какой код на самом деле скомпилирован для этого. В любом случае это не так важно, так как основной целью был перевод ASM на C, и проблема решена. Спасибо всем за участие.

Так что первые 2 строки в основном бесполезны, и к моему адресу добавляется только значение, и больше ничего. Мне нужно, чтобы он интерпретировался как адрес и извлекал значение на этом этапе.

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

Я знаю, что некоторым из вас это может показаться тривиальным, но я не очень знаком с C, поэтому я борюсь здесь ...

1 Ответ

0 голосов
/ 26 октября 2018

Вы можете упростить асм до одной инструкции, когда математика выполняется во время сборки. movabs rax, [0xdeadbeef + 0xa] может использовать AL / AX / EAX / RAX-форму mov, который загружается с 64-битного абсолютного адреса (https://felixcloutier.com/x86/MOV.html). (он не подходит для 32-битного расширенного знака disp32, потому что старший бит младшего 32 установлен, в отличие от обычных статических адресов в позиционно-зависимом коде.) Обычный mov с 32-битным переопределением размера адреса тоже будет работать, примерно в 7 байтах, потому что ваш адрес подходит в расширенном от нуля 32-разрядном целом числе.

В C вы также можете сделать все это одним утверждением . Не нужно слишком усложнять вещи: ваш адрес является указателем на указатель, поэтому вам нужно привести ваше целое число к типу x **.

void *ptr = *(const void**)(0xdeadbeefUL + 0xa);

В asm указатели являются просто целыми числами, поэтому имеет смысл выполнять математику, используя целые числа вместо char*. Отсутствие знака гарантирует, что он расширяется от нуля до ширины указателя, а не до знака.

(Числовые литералы в C имеют достаточно широкий тип для представления значения, поэтому 0xdeadbeef на компиляторе x86-64 будет int64_t (long long). На самом деле вы не получите 0xdeadbeef являющийся отрицательным 32-разрядным int со знаком, расширенным до 0xffffffffdeadbeef.)

Так как void не имеет размера, вы не можете добавлять / вычитать целые числа к void*. И указатель-математика на void ** будет в кусках sizeof(void*).

Чтобы избежать неопределенного поведения от разыменования void**, который не выровнен по 8 = alignof(void*) (в обоих основных x86-64 ABI), вы должны использовать memcpy. Но я предполагаю, что ваш пример адреса является лишь поддельным примером. Основные компиляторы x86, такие как gcc, не делают ничего странного с невыровненными адресами, чтобы наказать программистов за UB, поэтому выходные данные компилятора будут содержать невыровненные нагрузки, которые отлично работают на x86. Но при автоматической векторизации вы можете столкнуться с проблемами из такого рода UB. Почему при выравнивании доступа к памяти mmap иногда возникает ошибка на AMD64?


Но если вы по какой-то причине захотели разбить вещи на несколько операторов asm, вы можете транслитерировать это на несколько операторов C, например:

uintptr_t wheres_the_beef = 0xdeadbeef;    // mov eax, 0xdeadbeef
wheres_the_beef += 0xa;                    // add eax, 0xa
void **address = (void**)wheres_the_beef;  // purely a cast, no asm instructions;
void *ptr = *address;                      // mov rax, [rax]

Вы можете возиться с char*, если хотите добавить смещение байтов в указатели, но здесь нет никакого смысла.

Опять же, это все еще имеет неопределенное поведение в большинстве реализаций C, где alignof(void*) больше 1, поэтому void **address = (void**)wheres_the_beef создает неправильно выровненный указатель.

(Интересный факт: даже создание неправильно выровненных указателей - это UB в ISO C. Но все компиляторы x86, которые поддерживают встроенные функции Intel, должны поддерживать создание неправильно выровненных указателей для передачи их встроенным объектам, таким как _mm_loadu_ps(), поэтому потенциальная проблема заключается только в их разыменовании. на компиляторах x86.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...