Какъв е форматът на структурата x86_64 va_list?

Някой има ли справка за представянето на va_list в x86_64 ABI (този, който се използва в Linux)? Опитвам се да отстраня грешки в някакъв код, където стекът или аргументите изглеждат повредени и наистина би помогнало да разбера какво трябва да виждам...


person R.. GitHub STOP HELPING ICE    schedule 10.02.2011    source източник


Отговори (3)


Превърнах коментара си в отговор.

Това може да помогне . Това е препратка, макар и лека (РЕДАКТИРАНЕ: оригиналната връзка е мъртва; заменена връзка, запазена от Wayback Machine).

Препратката към списъка с променливи аргументи започва на страница 50, след което продължава, страници 52-53 документи va_list:

Тип va_list

Типът va_list е масив, съдържащ единичен елемент от една структура, съдържаща необходимата информация за прилагане на макроса va_arg. C дефиницията на типа va_list е дадена на фигура 3.34

// Figure 3.34
typedef struct {
   unsigned int gp_offset;
   unsigned int fp_offset;
   void *overflow_arg_area;
   void *reg_save_area;
} va_list[1];

Макрото va_start

Макросът va_start инициализира структурата, както следва:

reg_save_area Елементът сочи към началото на областта за запис на регистъра.

overflow_arg_area Този указател се използва за извличане на аргументи, предадени на стека. Той се инициализира с адреса на първия аргумент, предаден на стека, ако има такъв, и след това винаги се актуализира, за да сочи към началото на следващия аргумент на стека.

gp_offset Елементът съдържа отместването в байтове от reg_save_area до мястото, където е записан следващият наличен регистър на аргументи с общо предназначение. В случай, че всички регистри на аргументи са изчерпани, се задава стойност 48 (6 ∗ 8).

fp_offset Елементът съдържа отместването в байтове от reg_save_area до мястото, където е записан следващият наличен регистър на аргументи с плаваща запетая. В случай, че всички регистри на аргументи са изчерпани, се задава стойност 304 (6 ∗ 8 + 16 ∗ 16).

person Skurmedel    schedule 10.02.2011
comment
Почти съм сигурен, че тези регистри с „плаваща запетая“ всъщност са SSE регистри и че има само 8 от тях. - person Dave Abrahams; 20.12.2013
comment
@DaveAbrahams x86_64 има 16 общи и SSE регистъра. - person doug65536; 03.04.2021

Оказва се, че проблемът е, че gcc прави va_list тип масив. Моята функция беше на подписа:

void foo(va_list ap);

и исках да предам указател към ap към друга функция, така че направих:

void foo(va_list ap)
{
    bar(&ap);
}

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

За да заобиколя проблема, промених кода на:

void foo(va_list ap)
{
    va_list ap2;
    va_copy(ap2, ap);
    bar(&ap2);
    va_end(ap2);
}

Това е единственото преносимо решение, което можах да измисля, което отчита както възможността va_list да е тип масив, така и възможността да не е.

person R.. GitHub STOP HELPING ICE    schedule 10.02.2011
comment
Не можехте ли току-що да накарате foo да вземе аргумент от тип va_list *? - person caf; 11.02.2011
comment
@caf: foo има фиксиран подпис, който не мога да променя. И дори ако не, v* функциите винаги приемат va_list аргумент, а не va_list * аргумент. Това е стандартна конвенция и би било досадно за потребителите на функцията да я нарушават. - person R.. GitHub STOP HELPING ICE; 11.02.2011
comment
Защо трябваше да подадете указател към ap2 в извикването на bar()? - person Yann Droneaud; 11.02.2011
comment
Този код, разбира се, е съкратен, но въпросът е, че bar използва допълнителна информация, за да определи типа на следващия аргумент и го изважда от списъка с аргументи. Ако сте предали ap по стойност, вместо да подадете указател към него, всяко по-нататъшно използване на ap след bar връща, с изключение на va_end, ще доведе до недефинирано поведение. - person R.. GitHub STOP HELPING ICE; 11.02.2011
comment
@R Не съм сигурен, че виждам как предаването му чрез указател би помогнало - вашият случай звучи така, сякаш е точно това, за което е va_copy. - person bdonlan; 11.02.2011
comment
@bdonlan: bar се извиква повече от веднъж от foo и всяко повикване към bar трябва да види ефектите от предишното. Това е изрично UB (по ISO C), ако преминете va_list; от вас се изисква да подадете указател към va_list за тази употреба. - person R.. GitHub STOP HELPING ICE; 11.02.2011
comment
въпреки че това трябва да работи на практика, мисля, че технически все още е UB, тъй като va_copy() се използва с аргумент, който няма тип va_list: ако va_copy() не е функция (което е изрично разрешено), тогава няма да се извършват корекции на параметри (!); все пак може да се твърди, че тъй като стандартът дава прототип за va_copy() и посочва, че той може да бъде имплементиран като функция, имплицитно се приемат корекции на аргументи... - person Christoph; 08.11.2011
comment
не следя. Искате да кажете, че аргументът няма тип va_list, защото va_list е тип масив и че това прави използването на va_copy недефинирано? За какви настройки на параметрите говориш? Разпадането на типове масиви към указатели няма нищо общо с предаването им на функция... - person R.. GitHub STOP HELPING ICE; 09.11.2011
comment
@Christoph: Поглеждайки назад, виждам какво казахте, но не мисля, че това е проблем. Очевидното предназначение на va_copy е да го използвате върху va_list, което сте получили като аргумент. Ако сте го получили с va_start, можете просто да се обадите на va_start отново, за да получите копие и няма да има нужда от va_copy. - person R.. GitHub STOP HELPING ICE; 02.08.2013
comment
Не разбирам какво променя va_copy. Все още получавате va_list, така че защо изведнъж е добре да подадете показалеца към копието? РЕДАКТИРАНЕ: Сега разбрах основната идея... julio.meroh.net/2011/09/using-vacopy-to-safely-pass-ap.html - person polynomial_donut; 25.07.2018
comment
@polynomial_donut: Публикацията в блога, към която сте се свързали, е фалшива, както е отбелязано в актуализацията в горната й част. Винаги е добре да предадете va_list, което сте получили като аргумент, към друга функция, приемаща va_list (стига да не го използвате след това, освен към va_end). Също така е добре да подадете указател към va_list.... - person R.. GitHub STOP HELPING ICE; 25.07.2018
comment
... Проблемът е, че докато декларирането на обект va_list ap; ви дава обект от тип va_list, така че &ap да има тип указател-към-va_list, получаването на аргумент, деклариран va_list ap не е задължително< /b> означава ap има тип va_list. Ако va_list е тип масив, се прилагат правилата за разпадане на масива за аргументите на функцията и тогава ap има тип указател-към-указател-към-__typeof__(*ap), който не е нито съвместим с типа, нито правилната стойност за предаване на функция, очакваща указател към -va_list (това е допълнително ниво на индиректност). va_copy to temp е единственият начин да поправите това. - person R.. GitHub STOP HELPING ICE; 25.07.2018
comment
@R.. „Сега схванах идеята“ се отнасяше за примера, че може да бъде включена отместваща променлива към указател на стека. Този пример не беше фалшив в обяснението защо и кога разликата може да има значение - или поне ми прозвуча като разумно обяснение. От друга страна, предаването на указател към va_list е добре, ако C99 ни каже това или не (?). Поне бих искал да предположа, че clang или gcc са внедрили C99 според пълните спецификации (?). - person polynomial_donut; 25.07.2018
comment
@R.. Също така трябва да призная, че все още не виждам какъв е проблемът само с разпадането на аргумента, освен хвърлянето на sizeof към него и очакването на размера на целия обект с данни - но това няма да работи дори ако не предаде указател към типа на масива и не го дереферира (и просто предаде самия масив) (?) - person polynomial_donut; 25.07.2018
comment
@polynomial_donut: Променливата за отместване към указател на стека може да бъде включена. Линията на разсъждения беше напълно фалшива. Езикът позволява предаване на va_list към друга функция, така че такава реализация, която използва относителни отмествания от база, която може да знае само функцията, извикваща va_start, би била несъответстваща. - person R.. GitHub STOP HELPING ICE; 25.07.2018
comment
@polynomial_donut: В случай на аргумент на функция, деклариран va_list ap, където va_list е дефиниран с тип масив, ap няма тип va_list. Има тип __typeof__(*ap)*. Масивите не са указатели. Следователно &ap няма тип va_list * (указател към целия масив, чиято стойност е същата като указателя към началния член, но чийто тип е различен), който трябва да има за да го предадете на функция, очакваща va_list *. Вместо това &ap има тип __typeof__(*ap)**, тип указател към указател, чиято стойност няма нищо общо с адреса на обекта va_list. - person R.. GitHub STOP HELPING ICE; 25.07.2018
comment
@R.. първо, благодаря за търпението и отговорите :) От това, което си написал и прочел малко, какво се случва може да се сведе до: 1. При влизане в foo, ap се преобразува в typeof(*ap) * (съгласно C99) 2. След това bar се извиква на &ap, което е от тип typeof(*ap) ** 3. Ако приемем, че полученият аргумент вътре в bar е именуван ap_bar: Ако ap_bar се дереферира, резултатът е действителен масив, така че машинният код ще работи по различен начин върху него, когато използвате [] или *, отколкото ако беше __typeof__(*ap) ** (което наистина е във вашия пример). - person polynomial_donut; 25.07.2018
comment
Освен това ap_bar * подлежи на същите преобразувания на аргументи (масив в указател), както е споменато в точка 1; това преобразуване може да доведе до неправилни резултати, когато се приложи към променлива от тип __typeof__(*ap) *. - person polynomial_donut; 25.07.2018
comment
@polynomial_donut: Мисля, че би било поучително да разработите пример с typedef int foo[1]; и функции, приемащи този тип foo или указател към него като аргумент. - person R.. GitHub STOP HELPING ICE; 25.07.2018

В i386 архитектурата va_list е тип указател. В архитектурата AMD64 обаче това е тип масив. Каква е разликата? Всъщност, ако приложите операция & към тип указател, ще получите адреса на тази променлива на указателя. Но без значение колко пъти прилагате & операция към тип масив, стойността е същата и е равна на адреса на този масив.

И така, какво трябва да направите в AMD64? Най-лесният начин за предаване на променлива на va_list във функция е просто да я подадете без * или & оператор.

Например:

void foo(const char *fmt, ...) {
    va_list ap;
    int cnt;
    va_start(ap, fmt);
    bar(fmt, ap);
    va_end(ap);
    return cnt;
}
void bar(const char *fmt, va_list ap) {
    va_arg(ap, int);
    //do something
    test(ap);
}
void test(va_list ap) {
    va_arg(ap, int);
    //do something
}

Просто работи! И не е нужно да се притеснявате колко аргументи имате.

person Maple    schedule 16.09.2018
comment
Това не отговаря на въпроса. - person R.. GitHub STOP HELPING ICE; 16.09.2018