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

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

3 ответа

Решение

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

Это может помочь. Это ссылка, хотя и легковесная (РЕДАКТИРОВАТЬ: исходная ссылка не работает; заменена Wayback Machine-preserved link).

Ссылка на список аргументов переменных начинается на стр. 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 это тип массива и вероятность того, что это не так.

В архитектуре 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
}

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

Другие вопросы по тегам