Чтение значения регистра в переменную C - PullRequest
38 голосов
/ 22 января 2010

Я помню, как видел способ использования встроенной сборки gcc для чтения значения регистра и сохранения его в переменной C. Хотя я не могу на всю жизнь вспомнить, как сформировать ассемблерное утверждение. Буду признателен за любую оказанную помощь.

Ответы [ 8 ]

31 голосов
/ 22 января 2010

Идет в другом направлении, чем другие ответы, так как я не уверен, что вы хотите.

GCC Manual & sect; 5.40 Переменные в указанных регистрах

register int *foo asm ("a5");

Здесь a5 - это имя регистра, который должен использоваться & hellip;

Естественно, имя регистра зависит от процессора, но это не проблема, поскольку конкретные регистры чаще всего используются с явными инструкциями на ассемблере (см. Extended Asm ). Обе эти вещи обычно требуют, чтобы вы обусловили свою программу в соответствии с типом процессора.

Определение такой переменной регистра не резервирует регистр; он остается доступным для других целей в тех местах, где управление потоком определяет, что значение переменной не является действующим.

GCC Manual & sect; 3.18 Варианты для соглашений о генерации кода

-ffixed- р

Обрабатывать регистр с именем reg как фиксированный регистр; сгенерированный код никогда не должен ссылаться на него (за исключением, возможно, указателя стека, указателя кадра или какой-либо другой фиксированной роли).

Это может повторить ответ Ричарда более простым способом,

int main() {
    register int i asm("ebx");
    return i + 1;
}

хотя это довольно бессмысленно, так как вы не представляете, что находится в регистре ebx.

Если вы объединили эти два, скомпилировав это с gcc -ffixed-ebx,

#include <stdio.h>
register int counter asm("ebx");
void check(int n) {
    if (!(n % 2 && n % 3 && n % 5)) counter++;
}
int main() {
    int i;
    counter = 0;
    for (i = 1; i <= 100; i++) check(i);
    printf("%d Hamming numbers between 1 and 100\n", counter);
    return 0;
}

вы можете убедиться, что переменная C всегда использует резидентные значения в регистре для быстрого доступа и также не будет засорена другим сгенерированным кодом. (Удобно, что ebx сохраняется при обычном соглашении о вызовах в x86, поэтому даже если оно перекрывается вызовами других функций, скомпилированных без -ffixed-*, оно также должно быть восстановлено.)

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

19 голосов
/ 22 января 2010

Вот способ получить отлив:

int main()
{
    int i;
    asm("\t movl %%ebx,%0" : "=r"(i));
    return i + 1;
}

Результат:

main:
    subl    $4, %esp
    #APP
             movl %ebx,%eax
    #NO_APP
    incl    %eax
    addl    $4, %esp
    ret

<ч /> Edit:

"= r" (i) - это ограничение вывода, сообщающее компилятору, что первый вывод (% 0) является регистром, который должен быть помещен в переменную "i". На этом уровне оптимизации (-O5) переменная i никогда не сохраняется в памяти, но хранится в регистре eax, который также является регистром возвращаемого значения.

5 голосов
/ 22 января 2010

Я не знаю о gcc, но в VS это так:

int data = 0;   
__asm
{
    mov ebx, 30
    mov data, ebx
}
cout<<data;

По сути, я переместил данные в ebx в вашу переменную data.

3 голосов
/ 22 января 2010

Это переместит регистр указателя стека в переменную sp.

intptr_t sp;
asm ("movl %%esp, %0" : "=r" (sp) );

Просто замените 'esp' фактическим регистром, который вас интересует (но убедитесь, что не потеряете %%) иsp 'с вашей переменной.

2 голосов
/ 17 марта 2018

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

Как говорится, если вы собираетесь выполнить это странное задание, вы также можете сделать это эффективно.

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

#include <stdint.h>

int foo() {
    uint64_t rax_value;           // type width determines register size
    asm("" : "=a"(rax_value));  // =letter determines which register (or partial reg)

    uint32_t ebx_value;
    asm("" : "=b"(ebx_value));

    uint16_t si_value;
    asm("" : "=S"(si_value) );

    uint8_t sil_value;  // x86-64 required to use the low 8 of a reg other than a-d
       // With -m32:  error: unsupported size for integer register
    asm("# Hi mom, my output constraint picked %0" : "=S"(sil_value) );

    return sil_value + ebx_value;
}

Скомпилировано с clang5.0 на Godbolt для x86-64 . Обратите внимание, что 2 неиспользуемых выходных значения оптимизированы, а не #APP / #NO_APP сгенерированных компилятором пар asm-комментариев (которые переводят ассемблер / в режим быстрого анализа или, по крайней мере, используются, если это больше не вещь ). Это потому, что я не использовал asm volatile, и у них есть выходной операнд, поэтому они не являются неявно volatile.

foo():                                # @foo()
# BB#0:
    push    rbx
    #APP
    #NO_APP
    #DEBUG_VALUE: foo:ebx_value <- %EBX
    #APP
    # Hi mom, my output constraint picked %sil
    #NO_APP
    #DEBUG_VALUE: foo:sil_value <- %SIL
    movzx   eax, sil
    add     eax, ebx
    pop     rbx
    ret
                                    # -- End function
                                    # DW_AT_GNU_pubnames
                                    # DW_AT_external

Обратите внимание на сгенерированный компилятором код для добавления двух выходных данных напрямую из указанных регистров. Также обратите внимание на push / pop в RBX, потому что RBX - это сохраняемый вызовом регистр в соглашении о вызовах System V. (И в основном все 32 и 64-битные соглашения о вызовах x86). Но мы сказали компилятору, что наш оператор asm записывает туда значение. (Использование пустого оператора asm - это своего рода хак; нет синтаксиса для прямого указания компилятору, что мы просто хотим прочитать регистр, потому что, как я сказал, вы не знаете, что компилятор делал с регистрами, когда ваш оператор asm вставлено.) * * одна тысяча двадцать два

Компилятор будет обрабатывать ваш оператор asm так, как если бы он на самом деле записал этот регистр, поэтому, если ему понадобится значение на более позднее время, он скопирует его в другой регистр (или выгрузит в память), когда ваш asm оператор "бежит".


Другие x86 регистровые ограничения : b (bl / bx / ebx / rbx), c (... / rcx), d (... / rdx), S (sil / si / esi / rsi), D (... / rdi) . Для bpl / bp / ebp / rbp нет особых ограничений, даже если они не являются специальными в функциях без указателя кадра. (Возможно, потому что его использование сделает ваш код не компилятором с -fno-omit-frame-pointer.)

Вы можете использовать register uint64_t rbp_var asm ("rbp"), в этом случае asm("" : "=r" (rbp_var)); гарантирует, что ограничение "=r" выберет rbp. Аналогично для r8-r15, которые также не имеют явных ограничений. На некоторых архитектурах, таких как ARM, переменные asm-register являются единственным способом указать, какой регистр требуется для ограничений ввода / вывода asm. (И обратите внимание, что ограничения asm - это only поддерживаемое использование register asm переменных ; нет никакой гарантии, что значение переменной будет в этом регистре в любое другое время .


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

Вы можете поместить оператор asm в цепочку зависимостей для чего-то еще, чтобы контролировать, где компилятор помещает его . Используйте ограничение "+rm", чтобы сообщить компилятору, что он изменяет некоторую другую переменную, которая фактически используется для чего-то, что не оптимизируется.

uint32_t ebx_value;
asm("" : "=b"(ebx_value), "+rm"(some_used_variable) );

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

Это победит оптимизацию, такую ​​как постоянное распространение для этой переменной. https://gcc.gnu.org/wiki/DontUseInlineAsm. Компилятор не может принять что-либо о выходном значении; он не проверяет, что оператор asm содержит нулевые инструкции.


Это не работает для некоторых регистров, которые gcc не позволит вам использовать в качестве выходных операндов или сгустков, например. указатель стека.

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

В качестве альтернативы inline-asm есть __builtin_frame_address(0) для получения адреса стека. (Но, согласно IIRC, эта функция создает кадр полного стека, даже когда -fomit-frame-pointer включен, как по умолчанию в x86.)

Тем не менее, во многих функциях это почти бесплатно (а создание стекового кадра может быть полезно для размера кода из-за меньших режимов адресации для относительного RBP, чем относительного RSP-доступа к локальным переменным).

Использование инструкции mov в операторе asm также, конечно, сработает.

2 голосов
/ 17 марта 2018
#include <stdio.h>

void gav(){
        //rgv_t argv = get();
        register unsigned long long i asm("rax");
        register unsigned long long ii asm("rbx");
        printf("I`m gav - first arguman is: %s - 2th arguman is: %s\n", (char *)i, (char *)ii);
}

int main(void)
{
    char *test = "I`m main";
    char *test1 = "I`m main2";
    printf("0x%llx\n", (unsigned long long)&gav);
    asm("call %P0" : :"i"((unsigned long long)&gav), "a"(test), "b"(test1));
    return 0;
}
2 голосов
/ 22 января 2010

Из документации GCC: http://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html

0 голосов
/ 22 января 2010

Не это то, что вы ищете?

Синтаксис:

 asm ("fsinx %1,%0" : "=f" (result) : "f" (angle));
...