Почему разыменование указателя игнорируется в этом операторе встроенной сборки? - PullRequest
0 голосов
/ 22 октября 2018

В источниках XNU, в частности <libsyscall/os/tsd.h>, есть функция для быстрого доступа к локальным данным потока:

__attribute__((always_inline))
static __inline__ void*
_os_tsd_get_direct(unsigned long slot)
{
    void *ret;
    __asm__("mov %%gs:%1, %0" : "=r" (ret) : "m" (*(void **)(slot * sizeof(void *))));
    return ret;
}

Я запутался в том, как компилятор интерпретирует встроенную сборку.

Предположим, что slot == 1.На x86_64 sizeof(void *) == 8, поэтому выражение входного операнда становится *(void **)(8).Почему следующая разыменование не приводит к ошибке доступа к памяти?

Фактически, если я пытаюсь переместить выражение из оператора asm, я do получаю ошибку.

void * my_os_tsd_get_direct(unsigned long slot) {
    void *ret;
    void *ptr = *(void **)(slot * sizeof(void *));
    __asm__("mov %%gs:%1, %0" : "=r" (ret) : "m" (ptr));
    return ret;
}

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

Итак, я подумал, хорошо, давайте попробуем удалить явную разыменование вasm, потому что компилятор, кажется, игнорирует его.

void * my_os_tsd_get_direct_v2(unsigned long slot) {
    void *ret;
    __asm__("mov %%gs:%1, %0" : "=r" (ret) : "m" ((void *)(slot * sizeof(void *))));
    return ret;
}

Но , который производит error: invalid lvalue in asm input for constraint 'm'.

Может кто-нибудь пролить свет на то, что происходит?

1 Ответ

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

Почему следующая разыменование не приводит к ошибке доступа к памяти?

Поскольку вы используете его как операнд памяти для блока asm, который не разыскивает его напрямую, только относительно базы сегмента GS. База GS установлена ​​на любой виртуальный адрес, в котором мы хотим, чтобы блок локального хранилища потока этого потока был.

См. Как работает gcc `__thread`? и / или Адреса переменных локального хранилища потоков о том, как gcc в Linux реализует локальное хранилище потоков (TLS), используя регистр сегментов FS или GS.XNU явно делает то же самое, но использует встроенный asm вместо того, чтобы использовать встроенные функции GNU C. для потоковых данных.


Ограничение "m" несколько похоже на оператор C &: вместо этогозагружая объект в регистр, компилятор просто заменяет режим адресации, который ссылается на объект, в шаблон asm.

Поскольку этот шаблон asm не использует режим адресации напрямую, а вместо этого %%gs:,на самом деле не делает разыменование *(void **)(slot * sizeof(void *))), которое могло бы произойти, если бы вы присвоили его переменной в чистом C.

подстановки asm-шаблона являются чисто текстовыми.Вы можете сделать что-то вроде 16 + %0, чтобы получить доступ к области памяти на 16 байт перед операндом памяти.


Как обычно, это помогает посмотреть на вывод asm компилятора.Я поместил ваш код в проводник компилятора Godbolt (с gcc и clang) и удалил статический встроенный материал, чтобы мы могли видеть asm для автономного определения функции.

void*
_os_tsd_get_direct(unsigned long slot)
{
    void *ret;
    __asm__("mov %%gs:%1, %0\n\t"
            "nop  # operand 1 was %1" : "=r" (ret) : "m" (*(void **)(slot * sizeof(void *))));
    return ret;
}

собирается в

#gcc -O3
    mov %gs:0(,%rdi,8), %rax
    nop                       # operand 1 was 0(,%rdi,8)
    ret

Я использовал NOP вместо просто комментария, поэтому он все еще виден даже после того, как Godbolt удаляет строки только для комментариев.Часто удобно добавлять фиктивные комментарии, показывающие, какие были операнды шаблона (особенно, если вы когда-либо используете какие-либо инструкции с неявными операндами и хотите посмотреть, что компилятор выбрал для операндов, которые не упомянуты в шаблоне иначе).*

Здесь я добавил это только для того, чтобы указать, что 0(,%rdi,8), подставляемый компилятором, - это просто текст, который может идти везде, где вы его просите.Хитрость в том, что мы просим об этом сразу после %%gs:.


void *ptr = *(void **)(slot * sizeof(void *));

Это делает что-то совершенно другое.Вы фактически разыменовываете смещение TLS как указатель на плоское виртуальное адресное пространство (используя базу сегмента DS по умолчанию = 0).

Если вы хотите разбить его, вы должны сделать

void * separated_os_tsd_get_direct(unsigned long slot) {
    void *ret;
    unsigned long slot_offset = slot * sizeof(void*);
    void **gs_ptr = (void **)slot_offset;
    __asm__("mov %%gs:%1, %0" : "=r" (ret) : "m" (*gs_ptr));
    return ret;
}

компилируется в:

separated_os_tsd_get_direct(unsigned long):
    mov %gs:0(,%rdi,8), %rax
    ret

Очень важно, чтобы операнд шаблона asm был разыменованием указателя, а не локальным.При включенной оптимизации локальный объект может быть оптимизирован и превращен обратно в указатель с разыменованием исходного местоположения (если он написан с использованием семантики, которая делает это возможным, в отличие от вашей версии), но лучше убедиться, что это безопасно, избегая фактического разыменования, отличного отв выражении внутри ограничения "m"(*ptr).

...