Подтвердить что ты не робот

Чтение значения регистра в переменной C

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

Хотя я не могу на всю жизнь вспомнить, как сформировать ассемблерное утверждение.

4b9b3361

Ответ 1

Примечание редактора: этот способ использования локальной переменной register-asm теперь задокументирован GCC как "не поддерживаемый". Это все еще обычно работает на GCC, но порывает с лязгом. (Думаю, эта формулировка в документации была добавлена после публикации этого ответа.)

Глобальная версия переменной с фиксированным регистром требует больших затрат производительности для 32-битного x86, который имеет только 7 регистров GP-целых чисел (не считая указателя стека). Это уменьшило бы это до 6. Только учтите это, если у вас есть глобальная переменная, которая интенсивно использует весь ваш код.


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

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

register int *foo asm ("a5");

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

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

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

Руководство GCC & sect; 3.18 Варианты условных обозначений генерации кода

-ffixed-reg

  Обрабатывать регистр с именем 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-*, он также должен быть восстановлен.)

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

Ответ 2

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

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) переменная я никогда не сохраняется в памяти, а сохраняется в регистре eax, который также является регистром возвращаемого значения.

Ответ 3

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

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

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

Ответ 4

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

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

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

Ответ 6

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

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

В некоторых целях (например, в x86) вы можете использовать выходные ограничения конкретного регистра, чтобы сообщить компилятору, в каком регистре будет выход. Используйте выходное ограничение конкретного регистра с пустым шаблоном asm (нулевые инструкции), чтобы скажите компилятору, что ваш оператор 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 неиспользуемых выходных значения оптимизированы, нет пар TG42/TG43, генерируемых компилятором 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. в x86-64. (И в основном все 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 являются единственным поддерживаемым использованием переменных 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, конечно, также будет работать.

Ответ 7

#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;
}

Ответ 8

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

Синтаксис:

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