LINUX.ORG.RU

ARM va_args GCC

 , , , ,


0

2

Решение сабжевой проблемы от Azq2: [Link] и iliyap: [Link]!


Железка ARMv4, ARM7TDMI-S. Код собирается старым ARM ADS компилятором и работает. Я решил собирать код православным GCC вместо проприетарного барахла: arm-none-eabi-gcc (10.3.1). В железке есть прошивка и она скомпилирована ADS. Функции прошивки я дёргаю из своего кода, который компилю GCC. Проверил – всё работает, кроме variadic functions. Параметры компиляции у ADS и у GCC следующие:

# ADS
/opt/arm/bin/tcc -c UtilLogStringData.c -o UtilLogStringData.o -bigend -apcs /interwork -O2

# GCC
arm-none-eabi-gcc -mabi=aapcs -ffreestanding  -mbig-endian -march=armv4t -mtune=arm7tdmi-s -mthumb -mthumb-interwork -O2 -nostdlib -fshort-wchar -fshort-enums -fpack-struct=4 -fno-builtin -c UtilLogStringData.c -o UtilLogStringData.o

Сама функция:

void UtilLogStringData(const char *format, ...) {
	char buffer[255];
	va_list vars;

	va_start(vars, format);
	vsprintf(buffer, format, vars);
	va_end(vars);

	suLogData(0, 0x5151, 1, strlen(buffer) + 1, buffer);
}

ADS на этом даёт вот такую картину:

10901958 <UtilLogStringData>:
10901958:	b40f      	push	{r0, r1, r2, r3}
1090195a:	b580      	push	{r7, lr}
1090195c:	b0c2      	sub	sp, #264	; 0x108
1090195e:	a845      	add	r0, sp, #276	; 0x114
10901960:	0880      	lsrs	r0, r0, #2
10901962:	0080      	lsls	r0, r0, #2
10901964:	9001      	str	r0, [sp, #4]
10901966:	a802      	add	r0, sp, #8
10901968:	aa01      	add	r2, sp, #4
1090196a:	9944      	ldr	r1, [sp, #272]	; 0x110
1090196c:	f765 fdcc 	bl	10867508 <vsprintf>
10901970:	2000      	movs	r0, #0
10901972:	9001      	str	r0, [sp, #4]
10901974:	a802      	add	r0, sp, #8
10901976:	f765 fd47 	bl	10867408 <strlen>
1090197a:	aa02      	add	r2, sp, #8
1090197c:	9200      	str	r2, [sp, #0]
1090197e:	2201      	movs	r2, #1
10901980:	1c43      	adds	r3, r0, #1
10901982:	2000      	movs	r0, #0
10901984:	49b7      	ldr	r1, [pc, #732]	; (10901c64 <loadELF+0x2ae>)
10901986:	f491 fd5d 	bl	10593444 <suLogData>
1090198a:	b042      	add	sp, #264	; 0x108
1090198c:	bc80      	pop	{r7}
1090198e:	bc08      	pop	{r3}
10901990:	b004      	add	sp, #16
10901992:	4718      	bx	r3

GCC выдаёт следующее, падение происходит на vsprintf:

109018f4 <UtilLogStringData>:
109018f4:	b40f      	push	{r0, r1, r2, r3}
109018f6:	b510      	push	{r4, lr}
109018f8:	b0c4      	sub	sp, #272	; 0x110
109018fa:	aa46      	add	r2, sp, #280	; 0x118
109018fc:	ca02      	ldmia	r2!, {r1}
109018fe:	ac04      	add	r4, sp, #16
10901900:	0020      	movs	r0, r4
10901902:	9203      	str	r2, [sp, #12]
10901904:	f765 fe00 	bl	10867508 <vsprintf>
10901908:	0020      	movs	r0, r4
1090190a:	f765 fd7d 	bl	10867408 <strlen>
1090190e:	2201      	movs	r2, #1
10901910:	1c43      	adds	r3, r0, #1
10901912:	9400      	str	r4, [sp, #0]
10901914:	2000      	movs	r0, #0
10901916:	4904      	ldr	r1, [pc, #16]	; (10901928 <UtilLogStringData+0x34>)
10901918:	f491 fd94 	bl	10593444 <suLogData>
1090191c:	b044      	add	sp, #272	; 0x110
1090191e:	bc10      	pop	{r4}
10901920:	bc08      	pop	{r3}
10901922:	b004      	add	sp, #16
10901924:	4718      	bx	r3
10901926:	46c0      	nop			; (mov r8, r8)
10901928:	00005151 	andeq	r5, r0, r1, asr r1

Так собственно вопрос, можно ли как-то заставить GCC генерить код так как это делает ADS, чтобы всё работало? В чём вообще причины того что всё отлетело? Я-то конечно забил ASM-костылём скопированным с ADS, но хочется чтобы GCC начисто генерировал корректный код.

★★★★★

Последнее исправление: EXL (всего исправлений: 3)

arm-ассемблер не знаю поэтому разбираться в разнице листингов лень. Но есть предположение, что у gcc либо другое соглашение о вызове variadic-функций (я не понял, ты её только своим кодом вызываешь или она экспортируется модулям, скомпилированным другим компилятором), либо, что наверно даже более вероятно, другое внутреннее представление va_list. Сравни хотя бы sizeof(va_list) в этих двух компиляторах, ну или какие-нить проверки посложнее сделай.

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

для arm? для vararg (допустим x86)?

у ТС что-то с выравниванием не так и возможно с -march -mtune .. убрать все подозрительные места - stdcall хоть по слухам и игнорируется, но если его поставить хуже не будет

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

stdcall - это чисто x86 атрибут. Функция с этим аттрибутом сама чистит стек. Как функция с переменным количеством аттрибутов узнает сколько параметров ему передали в стеке?

anonymous
()

а вообще есть отладчик и vsnprintf для записи в фикс.буфер;

если результирующая строка чуть заезжает за границу buffer то результат мягко говоря непредсказуем. Может сработать, а может и нет. Возможно у вас второй вариант

MKuznetsov ★★★★★
()

Может попробовать скомпилировать вызов собственно variadic функции?

Что-то навроде:

void foo(...);

void bar(void)
{
foo("hi", "there", 1234, 4321, 0.5);
}

И такой же вариант где foo принимает аргумент. И потом уже посмотреть как в стек будут записаны эти параметры.

Это даёт больше информации о том, как устроена запись аргументов в стек, чем передача уже готового указателя.

a1ba ★★
()

возможно и не в компиляторе дело, в целом не очень безопасный код, может попробовать так

void UtilLogStringData(const char *format, ...) {
	char buffer[255];
        int nres;
	va_list vars;

	va_start(vars, format);
	nres = vsnprintf(buffer,sizeof(buffer), format, vars);
	va_end(vars);
        if (nres > 0) {
	   suLogData(0, 0x5151, 1, nres + 1, buffer);
        }
}
zekori
()
Ответ на: комментарий от anonymous

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

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

Этот код тоже не правильный.

https://man7.org/linux/man-pages/man3/fprintf.3.html

The functions snprintf() and vsnprintf() do not write more than size bytes (including the terminating null byte (‘\0’)). If the output was truncated due to this limit, then the return value is the number of characters (excluding the terminating null byte) which would have been written to the final string if enough space had been available. Thus, a return value of size or more means that the output was truncated.

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

Компилятор не знает смысл строки форматирования

gcc и шланг уже знают. Там для этого костылей добавили. Смотри атрибут format здесь: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html

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

Вообще, можно передавать количество аргументов. Можно сделать языковую конструкцию, возвращающую их число. Но сишники не ищут лёгких путей! То, что даже в C++ это всё уже есть в виде initializer_list, и проще сделать всем сишным файлам расширение .cc и использовать плюшки, сишников вообще не смущает.

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

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

Не особо очень удачный троллинг. Текущая реализация vararg явно лучше чем твои предложения.

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

Где ты тут троллинг увидел? Сишная реализация переменного числа аргументов – фабрика по отстрелу жоп, причём с очень непонятным профитом. Плюсовые variadic templates куда лучше и безопаснее, например, потому что не теряют информацию о типе.

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

Но сишники не ищут лёгких путей!

Просто сишники не ломают ABI, которым пользуются не только сишники.

Можно сделать языковую конструкцию, возвращающую их число.

Вот придумают сишники свой cXXdecl с определением количества параметров только языковыми средствами, то что делать языкам, ищущим только легкие пути (как растеры, хейтящие си)

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

Просто сишники не ломают ABI, которым пользуются не только сишники.

Кто-то кроме сишников пользуется сишным va_args, построенным челиком на макросах и дичайшей компиляторной магии, полной чада угара и кутежа? Серьёзно?

Алсо чувак, тут весь тред буквально о несовместимости ABI между двумя сишными компиляторами.

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

Твоё предложение передавать количество полностью бесполезно если вызываемая функция не знает тип этих аргументов. Передавать в функцию количество байт аргументов чтоб она потом смогла сделать stdcall передав это число в ret - чушь, потому что эффект от этого ровно тот же что от cdecl (add esp,X после call), но с лишней передачей и обработкой бесполезных чисел в процессе.

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

У сишного vararg я заметил только одну проблему - для его кроссплатформенной работы с произвольными типами вокруг va_arg надо делать обёртку, сравнивающую sizeof получаемого типа с sizeof(int) и делающую всякие костыли если меньше. На мой взгляд стоило эту логику встроить в компилятор.

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

Шаблоны может и безопаснее, и что? Используй их, кто мешает.

Спасибо, использую :3

А си - это синтаксическая обёртка к (иногда оптимизированному) ассемблеру, оставь его тем кто хочет именно это.

Си не является синтаксической обёрткой к ассемблеру с тех пор, как этот самый Си портировали за пределы PDP-11. То есть примерно последние 45 лет. Я готов поставить сотню баксов, что средний сишник (включая тебя, да) никогда даже близко не сможет предугадать, в какой ассемблерный листинг превратится любой данный кусок сишного говнокода. Особенно если в нём есть любимое сишниками UB.

У сишного vararg я заметил только одну проблему

Это потому что тебе надо к окулисту сходить. У сишного va_arg проблем куда больше чем одна.

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

Кто-то кроме сишников пользуется сишным va_args

практически все, кто дергает libc (например rust точно дергает ioctl)

тред буквально о несовместимости ABI между двумя сишными компиляторами.

Тред про несовместимость старого компилятора (проприетарного?) с новым (открытым)

anonymous
()

Непонятно, что значит «падение происходит на vsprintf». То ли это падение на инструкции вызова vsprintf (bl vsprintf), то ли это падение внутри vsprintf.

А что если попробовать вот так вместо #include <stdarg.h>:

/*#include <stdarg.h>*/
typedef int *va_list[1];
#define va_start(ap, parmN) (void)(*(ap) = (int*)(&parmN + 1))
#define va_end(ap) ((void)(*(ap) = 0))
iliyap ★★★★★
()
Ответ на: комментарий от anonymous

Да, анончик, ты на правильном пути. Макросы у ADS раскрываются вот так:

typedef int *var_list[1];

#define va_start(ap, parmN) (void)(*(ap) = __va_start(parmN))
#define va_end(ap) ((void)(*(ap) = 0))
EXL ★★★★★
() автор топика
Последнее исправление: EXL (всего исправлений: 1)
Ответ на: комментарий от anonymous

Кто-то кроме сишников пользуется сишным va_args

практически все, кто дергает libc (например rust точно дергает ioctl)

Я имел ввиду экспорт этих функций, не дёрганье Си в FFI. В Rust для этого специальный костыль, кстати.

Отдельно спасибо, что упомянул про ioctl, потому что нынешняя реализация ioctl и прочих syscall в сишечке – UB в точки зрения язычка. Тут даже тред был про это: Undefined behavior в MUSL

Тред про несовместимость старого компилятора (проприетарного?) с новым (открытым)

Ага, и эта несовместимость выражается именно в вот таком говне.

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

Большое спасибо! Мне тут в местном чате Azq2 подсказал похожее решение проблемы:

typedef __builtin_va_list *va_list;

#define va_start(a, b)       { __builtin_va_list tmp; __builtin_va_start(tmp, b); a = &tmp; }
#define va_end(a)            __builtin_va_end(*a)

В итоге оно компилируется в такое и всё работает:

109018f4 <UtilLogStringData>:
109018f4:	b40f      	push	{r0, r1, r2, r3}
109018f6:	b510      	push	{r4, lr}
109018f8:	b0c4      	sub	sp, #272	; 0x110
109018fa:	ab46      	add	r3, sp, #280	; 0x118
109018fc:	cb02      	ldmia	r3!, {r1}
109018fe:	ac04      	add	r4, sp, #16
10901900:	aa03      	add	r2, sp, #12
10901902:	0020      	movs	r0, r4
10901904:	9303      	str	r3, [sp, #12]
10901906:	f765 fdff 	bl	10867508 <vsprintf>
1090190a:	0020      	movs	r0, r4
1090190c:	f765 fd7c 	bl	10867408 <strlen>
10901910:	2201      	movs	r2, #1
10901912:	1c43      	adds	r3, r0, #1
10901914:	9400      	str	r4, [sp, #0]
10901916:	2000      	movs	r0, #0
10901918:	4903      	ldr	r1, [pc, #12]	; (10901928 <UtilLogStringData+0x34>)
1090191a:	f491 fd93 	bl	10593444 <suLogData>
1090191e:	b044      	add	sp, #272	; 0x110
10901920:	bc10      	pop	{r4}
10901922:	bc08      	pop	{r3}
10901924:	b004      	add	sp, #16
10901926:	4718      	bx	r3
10901928:	00005151 	andeq	r5, r0, r1, asr r1

И теперь я могу полностью выкинуть древний проприетарный ARM ADS, который даже не может скомпилировать такое:

UINT32 LdrLoadELF_AUX(W_CHAR *uri, W_CHAR *params) {
	UINT32 status;

	status = RESULT_OK;

	LdrLoadELF(uri, params);

	// "ElfLoader.c", line 461: Serious error: C2292E: typedef name 'UINT32' used in expression context
	// "ElfLoader.c", line 461: Serious error: C2284E: expected ';' after command - inserted before 'var'
	// "ElfLoader.c", line 461: Error: C2456E: undeclared name, inventing 'extern int var'
	// ElfLoader.c: 0 warnings, 1 error, 2 serious errors

	UINT32 var = 1;

	return status;
}
EXL ★★★★★
() автор топика
Последнее исправление: EXL (всего исправлений: 2)
Ответ на: комментарий от anonymous

Вызов variadic функции не означает что ты пользуешься va_args. Ими может пользоваться вызванная функция (внутри себя, снаружи это никак не видно), чтобы спарсить присланные аргументы, а может и не пользоваться (ioctl скорее всего второе).

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

ARM ADS, который даже не может скомпилировать такое:

Всё правильно он ругается, объявления переменных у нормальных людей расположены строго до кода. Не надо эти с++-манеры в си тащить.

#define va_start(a, b) { __builtin_va_list tmp; __builtin_va_start(tmp, b); a = &tmp; }

Сомнительная конструкция. Ну, может она конкретно у тебя и работает, но лучше этот tmp вынести наружу т.к. тут получается указатель на локальную переменную, которая закончила свою область видимости и срок жизни. По-моему компилятор вполне может её после закрывающей фигурной скобки оптимизировать и занять это место какой-то другой переменной, всё испортив.

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

Всё правильно он ругается, объявления переменных у нормальных людей расположены строго до кода. Не надо эти с++-манеры в си тащить.

А я тоже считаю, что Pascal и Ada – гораздо лучшие языки программирования, чем сраная сишечка!

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

Отдельно спасибо, что упомянул про ioctl, потому что нынешняя реализация ioctl и прочих syscall в сишечке – UB в точки зрения язычка.

Ох уж эти адепты чьей-то графомании.

Тут даже тред был про это: Undefined behavior в MUSL

Впрочем, по ссылке теоретически возможны проблемы: если функцию syscall() вызвали где-то в самом начале работы бинарника, с пустым стеком - va_arg() может обратиться за пределы верха стека и сделать сегфолт.

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

Всё правильно он ругается, объявления переменных у нормальных людей расположены строго до кода. Не надо эти с++-манеры в си тащить.

Половина сишных макросов работать перестанут, клоун.

gaylord
()
Ответ на: комментарий от iliyap

Проверил эквивалентность твоего и предложенного решения, дают одинаковый листинг:

109018f4 <ULSD>:
    push {r0, r1, r2, r3}              push {r0, r1, r2, r3}
    push {r4, lr}                      push {r4, lr}
    sub sp, #272 ; 0x110               sub sp, #272 ; 0x110
    add r3, sp, #280 ; 0x118           add r3, sp, #280 ; 0x118
    ldmia r3!, {r1}                    ldmia r3!, {r1}
    add r4, sp, #16                    add r4, sp, #16
    add r2, sp, #12                    add r2, sp, #12
    movs r0, r4                        movs r0, r4
    str r3, [sp, #12]                  str r3, [sp, #12]
    bl 10867508 <vsprintf>             bl 10867508 <vsprintf>
    movs r0, r4                        movs r0, r4
    bl 10867408 <strlen>               bl 10867408 <strlen>
    movs r2, #1                        movs r2, #1
    adds r3, r0, #1                    adds r3, r0, #1
    str r4, [sp, #0]                   str r4, [sp, #0]
    movs r0, #0                        movs r0, #0
    ldr r1, [pc, #12] ; (<ULSD+0x34>)  ldr r1, [pc, #12] ; (<ULSD+0x34>)
    bl 10593444 <suLogData>            bl 10593444 <suLogData>
    add sp, #272 ; 0x110               add sp, #272 ; 0x110
    pop {r4}                           pop {r4}
    pop {r3}                           pop {r3}
    add sp, #16                        add sp, #16
    bx r3                              bx r3
    andeq r5, r0, r1, asr r1           andeq r5, r0, r1, asr r1

Ещё раз спасибо. Вариант без использования builtin’ов и scope отличный и подходит как нельзя кстати.

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

Ох уж эти адепты чьей-то графомании.

Эти адепты тебе компилятор пишут, чувак. Тебе же жопу потом оторвёт, будешь тут про неправильные компиляторы рассказывать.

Впрочем, по ссылке теоретически возможны проблемы: если функцию syscall() вызвали где-то в самом начале работы бинарника, с пустым стеком - va_arg() может обратиться за пределы верха стека и сделать сегфолт.

Там автор правильно писал: в va_arg() может стоять счётчик, и при его проверке будет вызван abort(). И это абсолютно корректное поведение с точки зрения стандарта, которое сломает нахрен весь код на Си.

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

объявления переменных у нормальных людей расположены строго до кода.

Это ад для портирования гор СУЩЕСТВУЮЩЕГО кода.

И в ARM ADS это поведение изменить кстати нельзя никакими флагами. И кстати C99 тут тоже нет.

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

А и кстати, если тут дело действительно только в этом, то попробуй вместо этих хедер-хаков просто сделать так:

	vsprintf(buffer, format, &vars);

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

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

вместо этих хедер-хаков

А без них никак, мне же нужно соблюдать ту конвенцию variadic, что используется в прошивке, функции из которой я дёргаю. А она собрана ADS и её никак не поменять.

ElfLoader.c: In function 'UtilLogStringData':
ElfLoader.c:444:27: error: incompatible type for argument 3 of 'vsprintf'
  444 |  vsprintf(buffer, format, &vars);
      |                           ^~~~~
      |                           |
      |                           va_list * {aka __va_list *}
In file included from ElfLoader.c:17:
./SDK/utilities.h:189:59: note: expected 'va_list' {aka '__va_list'} but argument is of type 'va_list *' {aka '__va_list *'}
  189 | int vsprintf( char * buffer, const char * format, va_list arglist );
      |                                                   ~~~~~~~~^~~~~~~
EXL ★★★★★
() автор топика
Последнее исправление: EXL (всего исправлений: 1)
Ответ на: комментарий от firkax

Ничего не перестанет, не ври. Разве что те и так забагованные, которые макаки вроде тебя писали, не удосужившись обернуть блок в do-while(0)

И чо? Это все равно после кода. Либо крестик сними, либо трусы надень.

gaylord
()
Ответ на: комментарий от hateyoufeel

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

Там автор правильно писал: в va_arg() может стоять счётчик,

Какой ещё счётчик? Он в cdecl ABI не предусмотрен, а реализовать его уровнем выше невозможно.

Очевидно, ничего не сломается: если даже кто-то соорудит экзотическую архитектуру со счётчиком, он сам заметит что она несовместима с мейнстримными libc и пофиксит это ещё до её публиации.

firkax ★★★★★
()