Сборка: переменные стека C ++ адреса разные / неправильно?

Я не понимаю, почему получение адреса одной переменной в порядке, а другая получает 0xD, который затем падает из-за записи значения по недопустимому адресу (0xD в r.thefn(0);).

Это вывод, который показывает две переменные, не имеющие схожего адреса. Вот что показал GDB и вывод сборки. Моя сборка x86 невелика (я никогда не писал сборку x86). Я не знаю, показывает ли он достаточно информации, но если я не знаю, можете ли вы сказать мне, что еще требуется для отладки этого? Почему одна переменная 0xBF8BAF1C, а другая 0xD? C ++ и ассемблерный код приведены ниже, но лучше отформатированы в приведенной выше ссылке.

Eсть static_assert заставляя String быть POD что означает отсутствие нетривиальных конструкторов. Он использует конструктор по умолчанию, который генерирует C ++. Это также в стеке, что означает, что если new перегружен, это не повлияет на это. & не перегружен, но также выглядит корректно в первые два раза, когда вызывается функция.

Что может повлиять на r адрес? Я вижу переменную varmeадрес тот же, второй и третий раз, но третий раз r волшебно отличается.

Это компилируется и работает правильно с использованием Visual C ++ (работает 2012), g ++ 4.6.2, не работает Linux (Ubuntu) с использованием g ++ 3.7, 3.6.3 и лязг 3.0.

sanity check 1 0xbf8bb4cc
sanity check 2 0xbf8bb4cc 0xbf8bb538
sanity check 3 0xbf8bb4cc 0xbf8bb538
this 0xbf8bb538
sanity check 1 0xbf8baf1c
sanity check 2 0xbf8baf1c 0xbf8baf40
sanity check 3 0xbf8baf1c 0xbf8baf40
this 0xbf8baf40
sanity check 1 0xbf8baf1c
sanity check 2 0xbf8baf1c 0xd
sanity check 3 0xbf8baf1c 0xd
this 0xd

Вот код: Еще одно примечание — это static_assert on String, который обеспечивает, что это POD, что означает отсутствие конструктора не по умолчанию. Я проверил оператора & не перегружен

static int aa=0;
aa++;
int varme;

printf("sanity check 1 %p\n", &varme);
String r;
printf("sanity check 2 %p %p\n", &varme, &r);
//auto v=anotherfn(sz);
printf("sanity check 3 %p %p\n", &varme, &r);
//printf("callingfn=%s,%d %p %p\n", sz,aa, v, &r);
r.thefn(0);
return r;

¦0x8084101 <callingfn(char const*)+1> mov %esp,%ebp ¦
¦0x8084103 <callingfn(char const*)+3> push %esi ¦
¦0x8084104 <callingfn(char const*)+4> sub $0x34,%esp ¦
¦0x8084107 <callingfn(char const*)+7> mov 0xc(%ebp),%eax ¦
¦0x808410a <callingfn(char const*)+10> mov 0x8(%ebp),%ecx ¦
¦0x808410d <callingfn(char const*)+13> mov %eax,-0x8(%ebp) ¦
¦0x8084110 <callingfn(char const*)+16> mov 0x81bc894,%eax ¦
¦0x8084115 <callingfn(char const*)+21> lea 0x1(%eax),%eax ¦
¦0x8084118 <callingfn(char const*)+24> mov %eax,0x81bc894 ¦
¦0x808411d <callingfn(char const*)+29> lea -0xc(%ebp),%eax ¦
¦0x8084120 <callingfn(char const*)+32> mov %esp,%edx ¦
¦0x8084122 <callingfn(char const*)+34> mov %eax,0x4(%edx) ¦
¦0x8084125 <callingfn(char const*)+37> movl $0x812ee78,(%edx) ¦
¦0x808412b <callingfn(char const*)+43> mov %ecx,-0x10(%ebp) ¦
¦0x808412e <callingfn(char const*)+46> mov %eax,-0x14(%ebp) ¦
¦0x8084131 <callingfn(char const*)+49> call 0x8049a90 <printf@plt> ¦
¦0x8084136 <callingfn(char const*)+54> mov %esp,%ecx ¦
¦0x8084138 <callingfn(char const*)+56> mov -0x10(%ebp),%edx ¦
¦0x808413b <callingfn(char const*)+59> mov %edx,0x8(%ecx) ¦
¦0x808413e <callingfn(char const*)+62> mov -0x14(%ebp),%esi ¦
¦0x8084141 <callingfn(char const*)+65> mov %esi,0x4(%ecx) ¦
¦0x8084144 <callingfn(char const*)+68> movl $0x812ee8b,(%ecx) ¦
¦0x808414a <callingfn(char const*)+74> mov %eax,-0x18(%ebp) ¦
¦0x808414d <callingfn(char const*)+77> call 0x8049a90 <printf@plt> ¦
¦0x8084152 <callingfn(char const*)+82> mov %esp,%ecx ¦
¦0x8084154 <callingfn(char const*)+84> mov -0x10(%ebp),%edx ¦
¦0x8084157 <callingfn(char const*)+87> mov %edx,0x8(%ecx) ¦
¦0x808415a <callingfn(char const*)+90> mov -0x14(%ebp),%esi ¦
¦0x808415d <callingfn(char const*)+93> mov %esi,0x4(%ecx) ¦
¦0x8084160 <callingfn(char const*)+96> movl $0x812eea1,(%ecx) ¦
¦0x8084166 <callingfn(char const*)+102> mov %eax,-0x1c(%ebp) ¦
¦0x8084169 <callingfn(char const*)+105> call 0x8049a90 <printf@plt> ¦
¦
¦0x8084169 <callingfn(char const*)+105> call 0x8049a90 <printf@plt> ¦
¦0x808416e <callingfn(char const*)+110> mov %esp,%ecx ¦
¦0x8084170 <callingfn(char const*)+112> mov -0x10(%ebp),%edx ¦
¦0x8084173 <callingfn(char const*)+115> mov %edx,(%ecx) ¦
¦0x8084175 <callingfn(char const*)+117> movl $0x0,0x4(%ecx) ¦
¦0x808417c <callingfn(char const*)+124> mov %eax,-0x20(%ebp) ¦
¦0x808417f <callingfn(char const*)+127> call 0x8056d00 <SomeClass<blah>::thefn(blah*)> ¦
>¦0x8084184 <callingfn(char const*)+132> add $0x34,%esp ¦
¦0x8084187 <callingfn(char const*)+135> pop %esi ¦
¦0x8084188 <callingfn(char const*)+136> pop %ebp ¦
¦0x8084189 <callingfn(char const*)+137> ret $0x4 ¦
¦0x808418c nopl 0x0(%eax)

1

Решение

Настройка:

String определяется как:

struct String {
void *p;
#ifdef __cplusplus
/* Operators to help with comparing, etc. */
/* No additional data members */
void thefn(int arg); /* Return/argument type not relevant */
#endif
};

и включает в себя подтверждения для проверки sizeof(String) == sizeof(void *) и POD-сущность структуры.


Эта часть изначально не упоминалась в вопросе: функция, которая вызывает эту функцию, возвращает то же самое String объект для его вызывающего, но он вызывается из внешнего кода C, где вызывающий ожидает простой void * вместо String, Автор ожидал, что это должно сработать, потому что размер возвращаемого значения и формат совпадают.


Эта проблема:

Компилятор C ++ используется оптимизация именованных возвращаемых значений (NRVO) в этой функции. Сигнатура функции изменена с

String fn(char const *);

в

void fn(char const *, String *);

Это видно при разборке, где ebp+0xC читается, прежде чем писать в него, и нет никаких усилий, затраченных на внесение значимых результатов в EAX. ret 0x4 Эта часть была немного странной, поскольку она подразумевает, что только один аргумент удаляется из стека, но, очевидно, именно так GCC / Clang решают реализовать это, заставляя вызывающую функцию очистить дополнительный аргумент.

Предположительно, такая же оптимизация была применена в функции вызывающего абонента. Но компилятор C не видел причин применять эту оптимизацию (в конце концов, он ожидал, что результат будет void*, а не структура) и ожидал, что возвращаемое значение будет передано, как и любой результат размером с указатель.

В следствии:

  1. код C передает только один аргумент в код C ++, который ожидает два, и мусор в верхней части стека интерпретируется как второй аргумент.
  2. Код C ++ не дает значимого возвращаемого значения, когда код C ожидает его найти.

Решение:

Очевидный первый шаг к исправлению — убедиться, что код C ожидает того же возвращаемого значения, что и код C ++, структура вместо указателя.

тем не мение, Я не думаю, что есть способ контролировать, применяется ли NRVO, поэтому я подозреваю, что даже при правильном типе возврата все еще возможно, что две стороны кода будут применять эту оптимизацию непоследовательно, учитывая небольшой размер структуры. Я также понятия не имею, если extern "C" будет иметь какое-либо влияние на это.

(Этот ответ обобщает то, что было сказано в комментариях, с некоторыми догадками, чтобы заполнить пробелы)

6

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]