LINUX.ORG.RU

Баг GCC или что?

 


0

3
[kiv@kiv-hp15r161nr ~]$ cat test.c
#include <stdarg.h>
#include <stdbool.h>

void func(va_list arg) {
        bool value = va_arg(arg, bool);
        (void)value; // Нужно, чтобы оптимизатор не выпилил предыдущую строчку,
                // в реальном коде value используется нормально.
}
[kiv@kiv-hp15r161nr ~]$ gcc -O2 -c -o test.o test.c
[kiv@kiv-hp15r161nr ~]$ objdump -d test.o 

test.o:     формат файла elf64-x86-64


Дизассемблирование раздела .text:

0000000000000000 <func>:
   0:   0f 0b                   ud2 

Если заменить bool на int во втором аргументе va_arg, то код получается валидный. Версия GCC 5.3.0. Аналогичная ситуация на arm-none-eabi-gcc той же версии (собственно, всё началось с того что я искал несуществующую ошибку в своём коде для STM32).

Что это? Я где-то нарушил стандарт? Почему компилятор не выдал ни одного предупреждения? Да ладно бы он просто сгенерировал кривой код - почему он вообще вставляет просто недопустимую инструкцию (то есть портит код абсолютно осознанно)? WTF?

P. S. Добавление опций -Wpedantic -Wextra ничего не меняет (компилятор не ругается ни на что). Невалидная инструкция генерируется при любом уровне оптимизаций (разве что с -O0 добавляются ещё инструкции для создания стекового фрейма).

★★★★★

http://stackoverflow.com/questions/11336032/variable-argument-type-in-va-arg-...

В твоем случае странно, что не ругается, возможно bool ходит другими дорогами при компиляции.

upd: а, ну да, -Wall то у тебя нету, так можно и слона не заметить :)

arturpub ★★ ()
Последнее исправление: arturpub (всего исправлений: 1)
Ответ на: комментарий от arturpub

Мануал говорит, что -Wvarargs должен быть включен по дефолту. Попробуй явно указать и заодно проверить с char'ом. Если на одно ругается, а на другое нет, то имхо можно открывать issue.

arturpub ★★ ()
Ответ на: комментарий от arturpub
[kiv@kiv-hp15r161nr ~]$ cat test.c
#include <stdarg.h>
#include <stdbool.h>

void func1(va_list arg) {
        bool value = va_arg(arg, bool);
        (void)value;
}

void func2(va_list arg) {
        char value = va_arg(arg, char);
        (void)value;
}
[kiv@kiv-hp15r161nr ~]$ gcc -O2 -c -Wall -Wpedantic -Wextra -o test.o test.c
In file included from test.c:1:0:
test.c: В функции «func2»:
test.c:10:27: предупреждение: «char» преобразован к «int» при передаче через «...»
  char value = va_arg(arg, char);
                           ^
test.c:10:27: замечание: (поэтому «va_arg» нужно передать «int», а не «char»)
test.c:10:27: замечание: при достижении этого кода выполнение программы завершится аварийно
[kiv@kiv-hp15r161nr ~]$ objdump -d test.o

test.o:     формат файла elf64-x86-64
                                                                                                             
                                                                                                             
Дизассемблирование раздела .text:                                                                            
                                                                                                             
0000000000000000 <func1>:                                                                                    
   0:   0f 0b                   ud2                                                                          
   2:   0f 1f 40 00             nopl   0x0(%rax)                                                             
   6:   66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)                                                  
   d:   00 00 00 

0000000000000010 <func2>:
  10:   0f 0b                   ud2  

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

Как по мне либо он должен генерировать валидный код, либо выдавать предупреждение. А сейчас это какое-то молчаливое вредительство.

KivApple ★★★★★ ()
Последнее исправление: KivApple (всего исправлений: 2)

Что это? Я где-то нарушил стандарт?

В мане написано, что без va_start работать va_arg не должен.

monk ★★★★★ ()

А ты не хочешь va_start сделать?

intelfx ★★★★★ ()

va_arg(va_list ap, type);

The parameter ap is the va_list ap initialized by va_start().

andreyu ★★★★★ ()
Последнее исправление: andreyu (всего исправлений: 1)
Ответ на: комментарий от intelfx

Не-а. Где ты увидел у этой функции переменное число аргументов? va_start/va_end делает другая функция, которая имеет действительно переменное число аргументов.

Сейчас приведу простой пример:

int vprintf(const char *fmt, va_list arg) {
      ...
}

int printf(const char *fmt, ...) {
      va_list arg;
      va_start(arg, fmt);
      int r = vprintf(fmt, arg);
      va_end(arg);
      return r;
}

Именно такая логика у функций printf/vprintf в libc (разумеется, я всё упростил для наглядности).

Функция с переменным числом аргументов часто является лишь обёрткой над функцией принимающей va_list. Зачем это нужно? Например, за этим:

int debug_printf(const char *fmt, ...) {
      va_list arg;
      va_start(arg, fmt);
      fprintf(stderr, "DEBUG: ");
      int r = vfprintf(stderr, fmt, arg);
      va_end(arg);
      return r;
}

Опять же банальный пример, чтобы просто понять логику (ещё например, с помощью vsnprintf можно легко запилить свою реализацию asprintf). В общем, я всё правильно делаю (кроме того что пытаюсь получить bool через va_arg).

В реальном коде у меня было что-то подобное (была обёртка вокруг функции, которая принимала va_list), однако для рассматриваемого бага это не имеет никакого значения. Да, func нельзя вызывать напрямую, а надо через обёртку, которой нет, но суть то в том, что код некорректный генерируется (а обёртка гипотетически может быть в другом файле, так что на кодогенерацию её отсутствие не должно влиять).

KivApple ★★★★★ ()
Последнее исправление: KivApple (всего исправлений: 4)

Покажи минимальный рабочий (или, в данном случае, нерабочий) пример.

true_admin ★★★★★ ()
Ответ на: комментарий от i_gnatenko_brain

Когда писал описание бага багзилла gcc выдала похожую запись, но только для GCC 6.0 - https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67854

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

KivApple ★★★★★ ()
Ответ на: комментарий от true_admin

В ОП-посте же. В данном случае важно именно выдаст ли компилятор предупреждение или нет. Для char он точно также генерирует невалидный код, однако явно предупреждает об этом. Если тебе хочется именно программу, которая упадёт из-за этого бага:

$ cat test.c
#include <stdbool.h>
#include <stdarg.h>

void testFunc(int firstArg, ...) {
        (void)firstArg;
        va_list ap;
        va_start(ap, firstArg);
        bool secondArg = va_arg(ap, bool);
        (void)secondArg;
        va_end(ap);
}

int main(void) {
        testFunc(10, true);
        return 0
}
$ gcc -Wall -Wextra -O2 -o test test.c
$ ./test 
Недопустимая инструкция (core dumped)
$ gcc -c -Wall -Wextra -O2 -o test.o test.c

Либо компилятор должен выдать предупреждение, либо генерировать валидный код. В отличии от обычного UB, когда просто результат может быть странным, в данном случае компилятор делает код невалидным умышленно, вставляя недопустимый опкод. То есть где-то в коде GCC или stdarg.h есть фрагмент «если va_arg(bool), то сделать падующую прогу», такие вещи нельзя делать без предупреждений.

KivApple ★★★★★ ()
Последнее исправление: KivApple (всего исправлений: 2)
Ответ на: комментарий от KivApple

bool secondArg = va_arg(ap, bool);

Уже на этой строке мне vim (syntastic/ycm) говорит такое:

second argument to 'va_arg' is of promotable type 'bool'; this va_arg has undefined behavior because arguments will be promoted

andreyu ★★★★★ ()
Ответ на: комментарий от KivApple

А это выхлоп команты make t.c

$ make t
cc     t.c   -o t
t.c:17:30: warning: second argument to 'va_arg' is of promotable type 'bool'; this va_arg has undefined behavior because arguments will be
      promoted to 'int' [-Wvarargs]
        bool secondArg = va_arg(ap, bool);
                                    ^~~~
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/clang/7.0.2/include/stdbool.h:31:14: note:
      expanded from macro 'bool'
#define bool _Bool
             ^~~~~
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/clang/7.0.2/include/stdarg.h:35:50: note:
      expanded from macro 'va_arg'
#define va_arg(ap, type)    __builtin_va_arg(ap, type)
                                                 ^
1 warning generated.

Но да, это не gcc, а llvm:

$ gcc -v
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 7.0.2 (clang-700.1.81)
Target: x86_64-apple-darwin15.3.0
Thread model: posix
andreyu ★★★★★ ()
Ответ на: комментарий от KivApple

Еще в мане есть такой кусок кода:

case 'c': /* char */
   /* Note: char is promoted to int. */
   c = va_arg(ap, int);

andreyu ★★★★★ ()
Последнее исправление: andreyu (всего исправлений: 1)
Ответ на: комментарий от KivApple

Всё правильно. Вернее, всё неправильно, репорти баг :).

PS а clang работает. Я всем рекомендую использовать clang хотя бы для нахождения таких вот граблей. С gcc и не такие грабли бывают :(

true_admin ★★★★★ ()
Ответ на: комментарий от KivApple

А, блин. Не заметил, что ты va_list в функцию передаёшь. Претензий больше нет.

intelfx ★★★★★ ()
Ответ на: комментарий от andreyu

Я не утверждаю, что я написал идеальный код, однако я считаю поведение gcc некорректным в данной ситуации, с учётом того что о всяких char и short он предупреждает. Одно дело когда код работает, но не так как хотелось бы (всякие i++ + ++i и прочие UB, хотя про них тоже стоит предупреждать в идеале), а другое дело когда компилятор умышленно делает его 100% нерабочим и молчит об этом.

KivApple ★★★★★ ()
Последнее исправление: KivApple (всего исправлений: 4)
Ответ на: комментарий от intelfx

Просто так пример кода и ассемблерный листинг короче получаются и баг лучше видно, чем если я буду городить полный набор va_start и товарищей.

KivApple ★★★★★ ()
Последнее исправление: KivApple (всего исправлений: 1)
Ответ на: комментарий от KivApple

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

Напомнило негодование Линуса, когда он провозился с поиском бага в ядре, потом в gcc, а оказалось багрепорт у них уже некоторое время в багзиле валялся. Вот и получается, какой-то маргинальный баг, который кто-то обнаружил, не интересно фиксить: а кому оно надо? Но с другой стороны, когда какой-нибудь большой проект вдруг о него споткнётся, это будет дорого для проекта да глупо и как-то стыдно для gcc.

Так ты багрепорт пингнул? Хотя уже встречал негодование разработчиков, мол они в курсе (и багзила всё ещё не поддерживает +1). Вопрос только, а сколько месяцев/лет можно быть просто в курсе не фикся, а только пиля и пиля всё новые и новые фичи и... баги.

gag ★★★★★ ()
Ответ на: комментарий от KivApple

Либо компилятор должен выдать предупреждение, либо генерировать валидный код.

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

gag ★★★★★ ()
Ответ на: комментарий от gag

компилятор не должен в принципе генерировать бракованный код (невалидные инструкции)

Почему так сделано, описано в gcc-багзилле:

/* Unfortunately, this is merely undefined, rather than a constraint violation, so we cannot make this an error. If this call is never executed, the program is still strictly conforming. */

Видимо, для помощи отладке решили вставить ud2 вместо nop.

vzzo ★★★ ()
Ответ на: комментарий от vzzo

Зачем nop? Можно было бы выдать ворнинг, а самим привести тип к int (или во что там превращается bool, char и short при передаче через ... - компилятору это достоверно известно) и программа вообще работала бы корректно.

KivApple ★★★★★ ()
Последнее исправление: KivApple (всего исправлений: 1)
Ответ на: комментарий от KivApple

Там же в комментарии описана проблема. Компилятор подавляет предупреждения из системных заголовков (видимо, предполагая, что там ошибок нету), чтобы они не мешались в выводе всех программ. bool определён как _Bool в системном заголовке. Из-за этого при анализе предупреждение подавляется.

xaizek ★★★★★ ()
Ответ на: комментарий от KivApple

Можно было бы выдать ворнинг, а самим привести тип к int

В целом, согласен. Но, видимо, они за правильный код.

vzzo ★★★ ()
Ответ на: комментарий от gag

проекта да глупо и как-то стыдно для gcc.

Это будет поводом перейти на clang, поддерживаемый крупными компаниями, вроде Google и Apple, которые быстро пофиксят подобные баги, ибо там люди на зарплатках сидят.

EXL ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.