Добавление ведущих символов подчеркивания к символам сборки с помощью GCC на Win32?

У меня есть фрагмент кода C, который вызывает функцию, определенную в ассемблере. В качестве примера предположим, что foo.c содержит:

int bar(int x);  /* returns 2x */
int main(int argc, char *argv[]) { return bar(7); }

И bar.s содержит реализацию bar() в сборке x86:

.global bar
bar:    movl 4(%esp), %eax
        addl %eax, %eax
        ret

В Linux я могу легко скомпилировать и связать эти исходники с GCC следующим образом:

% gcc -o test foo.c bar.s
% ./test; echo $?
14

В Windows с MinGW это не удается с ошибкой «неопределенная ссылка на `bar'». Оказывается, причина этого в том, что в Windows все идентификаторы функций с соглашением о вызовах C имеют префикс подчеркивания, но поскольку «bar» определен в сборке, он не получает этот префикс, и связывание завершается ошибкой. (Таким образом, сообщение об ошибке на самом деле жалуется на отсутствие символа _bar, а не bar.)

Обобщить:

% gcc -c foo.c bar.s
% nm foo.o bar.o
foo.o:
00000000 b .bss
00000000 d .data
00000000 t .text
         U ___main
         U _bar
00000000 T _main

bar.o:
00000000 b .bss
00000000 d .data
00000000 t .text
00000000 T bar

Теперь вопрос: как я могу решить это красиво? Если бы я писал только для Windows, я мог бы просто добавить подчеркивание к идентификатору в bar.s, но тогда код ломается в Linux. Я просмотрел параметры gcc -fleading-underscore и -fno-leading-underscore, но ни один из них ничего не делает (по крайней мере, в Windows).

Единственная альтернатива, которую я вижу сейчас, — это пропустить файл сборки через препроцессор C и переопределить все объявленные символы вручную, если WIN32 определен, но это тоже не очень красиво.

У кого-нибудь есть чистое решение для этого? Возможно, вариант компилятора, который я курировал? Может быть, ассемблер GNU поддерживает способ указать, что этот конкретный символ относится к функции, использующей соглашение о вызовах C, и должен быть искажен как таковой? Любые другие идеи?


person Maks Verver    schedule 23.06.2009    source источник


Ответы (4)


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

  • -fleading-underscore

    Этот параметр и его аналог -fno-leading-underscore принудительно изменяют способ представления символов C в объектном файле. Одно из применений — помочь связать устаревший ассемблерный код.

    Внимание! Переключатель -fleading-underscore заставляет GCC генерировать код, несовместимый в двоичном виде с кодом, сгенерированным без этого ключа. Используйте его, чтобы соответствовать двоичному интерфейсу приложения, отличному от используемого по умолчанию. Не все цели обеспечивают полную поддержку этого переключателя.

Другой, более безопасный вариант — явно указать GCC имя для использования.

5.39 Управление именами, используемыми в коде на ассемблере

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

     int foo asm ("myfoo") = 2;

Это указывает, что имя, которое будет использоваться для переменной foo в коде ассемблера, должно быть ``myfoo' rather than the usual \``_foo'.

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

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

Вы не можете использовать asm таким образом в определении функции; но вы можете получить тот же эффект, написав объявление функции перед ее определением и поставив там asm, например:

 extern func () asm ("FUNC");

 func (x, y)
      int x, y;
 /* ... */

Вы должны убедиться, что выбранные вами имена ассемблера не конфликтуют с какими-либо другими символами ассемблера. Кроме того, вы не должны использовать регистрационное имя; это привело бы к совершенно недопустимому коду на ассемблере. GCC пока не имеет возможности хранить статические переменные в регистрах. Возможно, это будет добавлено.

В твоем случае,

extern int bar(int x) asm("bar");

должен сообщить GCC, что "bar использует ассемблерное имя ``bar`', несмотря на то, что это функция вызова".

person ephemient    schedule 24.06.2009
comment
Означает ли это, что поведение GCC по умолчанию в Linux должно заключаться в том, что имена C имеют начальное подчеркивание? Если да, то в среде сборки OP есть что-то, что его отключает? - person Michael Burr; 24.06.2009
comment
В Linux функции, соответствующие стандартному соглашению о вызовах C (ccall, cdecl, как бы вы это ни называли), не декорируются. В Windows stdcall является соглашением о вызовах по умолчанию, а функции, следующие за любым другим (например, стандартным соглашением о вызовах C), декорируются. - person ephemient; 24.06.2009
comment
Как я уже сказал, опции -fleading-underscore и -fno-leading-underscore, похоже, ничего не делают (они не удаляют подчеркивания в функциях C и не добавляют их для ассемблерных символов); если вы немного погуглите, вы увидите, что у других был такой же опыт, поэтому у меня сложилось впечатление, что эти варианты довольно бесполезны. Предложение asm() хорошее; Я вполне могу в конечном итоге использовать это. Единственным недостатком является то, что сам символ по-прежнему не имеет правильного имени (которое в Windows было бы _bar), но, по крайней мере, я могу связать его на обеих платформах без дальнейших модификаций исходного кода. - person Maks Verver; 24.06.2009
comment
Вам не нужно extern в прототипе. godbolt.org/z/kpFShC показывает, что void start(void) asm("_mystart"); работает. Кроме того, вы, вероятно, захотите использовать имя типа в своем примере вместо использования устаревшего C default-int. - person Peter Cordes; 20.02.2020

Вы можете использовать препроцессор C для предварительной обработки сборки и использовать макрос для добавления отсутствующих символов подчеркивания в Windows. Во-первых, вам нужно переименовать файл сборки из bar.s в bar.S (заглавная буква «S»). Это говорит gcc использовать cpp для предварительной обработки файла.

Чтобы добавить недостающие символы подчеркивания, вы можете определить макрос «cdecl» следующим образом:

#if defined(__WIN32__)
# define cdecl(s) _##s
#else
# define cdecl(s) s
#endif

Затем используйте его следующим образом:

.global cdecl(bar)
cdecl(bar):
    movl 4(%esp), %eax
    addl %eax, %eax
    ret

Обратите внимание, что Mac OSX также требует подчеркивания в начале, поэтому вы можете обновить первую строку макроса следующим образом:

#if defined(__WIN32__) || defined(__APPLE__)
person Geert    schedule 13.04.2012

Вы можете объявить это дважды?

.global bar
.global _bar

Я давно не писал ассемблер, но действует ли идентификатор .global как метка?

person Carson Myers    schedule 23.06.2009
comment
Директива .global указывает только, что этот идентификатор относится к глобальному символу, поэтому его можно заставить работать, если я также определю две метки для функции, например: .global bar .global _bar bar: _bar: ‹etc› Помимо дублирование, я также получаю бесполезный символ полосы в Windows и бесполезный символ _bar в Linux. Я надеялся на что-то более чистое, но это работает, поэтому я благодарю вас за предложение. - person Maks Verver; 24.06.2009

Компиляторы для цели ELF по умолчанию не добавляют начальные символы подчеркивания. Вы можете добавить -fleading-underscore при компиляции в формат ELF (под Linux). Используйте условное выражение в make-файле.

Ссылка: http://opencores.org/openrisc,gnu_toolchain#source (выполните поиск страницы по запросу «оставить глобальные имена без изменений»)

person Unsigned    schedule 19.10.2011