У кого-нибудь есть ссылка на представление va_list
в ABI x86_64 (тот, который используется в Linux)? Я пытаюсь отладить некоторый код, где стек или аргументы кажутся поврежденными, и это действительно помогло бы понять, что я должен видеть...
Каков формат структуры x86_64 va_list?
Ответы (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).
Оказывается, проблема заключалась в том, что 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
является типом массива, так и возможность того, что это не так.
foo
принимать аргумент типа va_list *
?
- person caf; 11.02.2011
foo
имеет фиксированную подпись, которую я не могу изменить. И даже если нет, функции v*
всегда принимают аргумент va_list
, а не аргумент va_list *
. Это стандартное соглашение, и пользователям функции будет неприятно его нарушать.
- person R.. GitHub STOP HELPING ICE; 11.02.2011
bar
использует дополнительную информацию для определения типа следующего аргумента и выталкивает его из списка аргументов. Если вы передадите ap
по значению вместо указателя на него, любое дальнейшее использование ap
после возврата bar
, кроме va_end
, приведет к неопределенному поведению.
- person R.. GitHub STOP HELPING ICE; 11.02.2011
va_copy
.
- person bdonlan; 11.02.2011
bar
вызывается foo
более одного раза, и каждый вызов bar
должен учитывать последствия предыдущего. Это явно UB (в соответствии с ISO C), если вы передаете va_list
; вам необходимо передать указатель на va_list
для этого использования.
- person R.. GitHub STOP HELPING ICE; 11.02.2011
va_copy()
используется с аргументом, который не имеет типа va_list
: если va_copy()
не является функцией (что явно разрешено), то никакие настройки параметров не будут выполняться (!); тем не менее, можно возразить, что, поскольку стандарт дает прототип для va_copy()
и заявляет, что он может быть реализован как функция, неявно предполагаются корректировки аргументов...
- person Christoph; 08.11.2011
va_list
, потому что va_list
является типом массива, и это делает использование va_copy
неопределенным? О каких параметрах вы говорите? Распад типов массивов на указатели не имеет ничего общего с их передачей функции...
- person R.. GitHub STOP HELPING ICE; 09.11.2011
va_copy
состоит в том, чтобы использовать его в va_list
, которое вы получили в качестве аргумента. Если бы вы получили его с помощью va_start
, вы могли бы просто снова позвонить va_start
, чтобы получить копию, и va_copy
не было бы необходимости.
- person R.. GitHub STOP HELPING ICE; 02.08.2013
va_copy
. Вы по-прежнему получаете va_list
, так почему вдруг можно передать указатель на копию? РЕДАКТИРОВАТЬ: теперь у меня есть основная идея... julio.meroh.net/2011/09/using-vacopy-to-safely-pass-ap.html
- person polynomial_donut; 25.07.2018
va_list
, которое вы получили в качестве аргумента, другой функции, принимающей va_list
(если вы не используете ее после этого, кроме va_end
). Также можно передать указатель на va_list
....
- person R.. GitHub STOP HELPING ICE; 25.07.2018
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
va_list
допустима, если C99 говорит нам об этом или нет (?). По крайней мере, я хотел бы предположить, что clang
или gcc
внедрили C99 в соответствии с полными спецификациями (?).
- person polynomial_donut; 25.07.2018
sizeof
и ожидает размер всего объекта данных, но это не сработает, даже если вы не передал указатель на тип массива и не разыменовал его (а просто передал сам массив) (?)
- person polynomial_donut; 25.07.2018
va_list
другой функции, поэтому такая реализация, которая использует относительные смещения от базы, которую может знать только функция, вызывающая va_start
, будет несоответствующей.
- person R.. GitHub STOP HELPING ICE; 25.07.2018
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
foo
ap
преобразуется в typeof(*ap) *
(согласно C99) 2. Затем bar
вызывается для &ap
, который имеет тип typeof(*ap) **
3. Предположим, что полученный аргумент внутри bar
называется ap_bar
: если ap_bar
разыменован, результатом будет фактический массив, поэтому машинный код будет работать по-другому на нем при использовании []
или *
, чем если бы это был __typeof__(*ap) **
(что действительно так в вашем примере).
- person polynomial_donut; 25.07.2018
ap_bar *
подвергается тем же преобразованиям аргументов (массив в указатель), что и в пункте 1; это преобразование может привести к неправильным результатам при применении к переменной типа __typeof__(*ap) *
.
- person polynomial_donut; 25.07.2018
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
}
Это просто работает! И вам не нужно беспокоиться о том, сколько аргументов у вас есть.