Каков формат структуры x86_64 va_list?

У кого-нибудь есть ссылка на представление va_list в ABI x86_64 (тот, который используется в Linux)? Я пытаюсь отладить некоторый код, где стек или аргументы кажутся поврежденными, и это действительно помогло бы понять, что я должен видеть...


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


Ответы (3)


Я превратил свой комментарий в ответ.

Это может помочь . Это ссылка, хотя и облегченная (EDIT: исходная ссылка мертва; заменена сохраненной ссылкой 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
Тем не менее, по-видимому, C99 говорит нам, что не должно быть проблем с передачей указатель . - 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