gdb выводит недопустимый адрес статических константных массивов нестроковых значений для классов с виртуальными функциями

#c #gdb

#c #gdb

Вопрос:

РЕДАКТИРОВАТЬ: Пожалуйста, прокрутите вниз до раздела «РЕДАКТИРОВАТЬ» в конце вопроса для получения более подробной информации. Я не редактирую остальную часть этого поста, чтобы сохранить историю комментариев.

У меня есть класс, определенный подобным образом в файле заголовка:

 class TestClass
{
public:
  TestClass() { }
  ~TestClass() { }
  void Test();
private:
  static const char * const carr[];
  static const int iarr[];
};
  

TestClass::Test() Функция просто проверяет, используются ли оба массива, чтобы они не были оптимизированы, — выводит их в журнал. Я не буду публиковать это здесь для ясности. Массивы инициализируются в cpp-файле.

Приведенный выше случай работает нормально, при создании экземпляра этого класса адреса выглядят следующим образом:

 t   TestClass * 0x20000268  
    carr    const char * const[]    0x8002490 <TestClass::carr> 
    iarr    const int []    0x800249c <TestClass::iarr>
  

Адреса памяти, начинающиеся с 0x20... , принадлежат области RAM, в то время как 0x80... принадлежат ROM / Flash. Как и ожидалось, оба массива помещаются в ПЗУ.

Однако, если я добавлю virtual квалификатор к любой функции в классе, например, его деструктор, например, так:

 class TestClass
{
public:
  TestClass() { }
  virtual ~TestClass() { }
  void Test();
private:
  static const char * const carr[];
  static const int iarr[];
};
  

Тогда результат будет таким:

 t   TestClass * 0x20000268  
    carr    const char * const[3]   0x80024b4 <TestClass::carr> 
    iarr    const int [1000]    0x20000270
  

В частности — iarr помещается в оперативную память, что совершенно не то, что я ожидал.

Этот файл скомпилирован следующим образом:

 arm-none-eabi-g   -mcpu=cortex-m7 -mthumb -mfloat-abi=soft -O0 -fmessage-length=0 -fsigned-char -ffunction-sections -fdata-sections -ffreestanding -fno-move-loop-invariants -Wall -Wextra -g3 -DDEBUG -DUSE_FULL_ASSERT -DTRACE -DOS_USE_TRACE_ITM -DSTM32F767xx -DUSE_HAL_DRIVER -DHSE_VALUE=24000000 -I../include -I../system/include -I../system/include/cmsis -I../system/include/stm32f7-hal -std=gnu  11 -fabi-version=0 -fno-exceptions -fno-rtti -fno-use-cxa-atexit -fno-threadsafe-statics -c -o "src\main.o" "..\src\main.cpp" 
  

И связующая часть:

 arm-none-eabi-g   -mcpu=cortex-m7 -mthumb -mfloat-abi=soft -O0 -fmessage-length=0 -fsigned-char -ffunction-sections -fdata-sections -ffreestanding -fno-move-loop-invariants -Wall -Wextra  -g3 -T mem.ld -T libs.ld -T sections.ld -nostartfiles -Xlinker --gc-sections -L"../ldscripts" -Wl,-Map,"VirtualClassTestF7.map" --specs=nano.specs -o "VirtualClassTestF7.elf" "@objs.rsp"  
  

В этом проекте создано больше файлов, связанных с аппаратной инициализацией. Я не включаю их, чтобы сделать сообщение коротким.

Есть ли какой-либо переключатель, который управляет этим поведением? Я уже пробовал очевидные части, которые я мог придумать, которые могли бы иметь малейшую связь с проблемой:

  • Уровни оптимизации: O0, O1, O2, O3, Os, Ofast
  • Удаление -ffunction-sections и -fdata-sections
  • Добавление -fno-common
  • Увеличение размера массива для превышения некоторого порога, если таковой имеется. Я увеличил его размер на 10 тыс. элементов (умноженный на sizeof(uint32_t)), и он все еще был в оперативной памяти
  • Trying three different versions of toolchain

Toolchain is arm-none-eabi . Tried versions (outputs of arm-none-eabi-gcc --version ):

  • arm-none-eabi-gcc.exe (GNU Tools for ARM Embedded Processors) 4.9.3 20150529 (release) [ARM/embedded-4_9-branch revision 224288]
  • arm-none-eabi-gcc.exe (bleeding-edge-toolchain) 7.2.0
  • arm-none-eabi-gcc.exe (bleeding-edge-toolchain) 8.3.0
  • Cygwin (gcc (GCC) 7.4.0)

First one comes from official ARM website: https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads. Last two come from http://www.freddiechopin.info/en/download/category/11-bleeding-edge-toolchain as ARM officially doesn’t release 64-bit version and our project grew to size that breaks the 32-bit version.

Почему это проблема и почему я специально изучаю переключатель компилятора: возможно, есть другой способ принудительно ввести эти значения в ПЗУ, записав его немного по-другому. Это не вариант — недавно мы столкнулись с этой проблемой в более крупном проекте, охватывающем тысячи файлов, где в различных местах интенсивно используется наследование классов. Не может быть и речи о том, чтобы перехватить все возможные вхождения таких массивов (некоторые создаются с помощью макросов, некоторые с помощью внешних инструментов), а затем реорганизовать весь этот код. Поэтому я ищу причину, по которой компилятор ведет себя именно таким образом, и каковы возможные решения, которые не требуют прикосновения к исходным файлам.

РЕДАКТИРОВАТЬ: Похоже, что это какая-то проблема с gdb и тем, как он извлекает адрес этой переменной, или я что-то упускаю. Я пошел дальше и создал тот же пример на ПК (Cygwin gcc 7.4.0):

 #include <stdio.h>

class TestClass
{
public:
  TestClass() { }
  virtual ~TestClass() { }
  static const char * const carr[];
  static const int iarr[];
};

const char * const TestClass::carr[] = {
    "test1", "test2", "test3"
};

const int TestClass::iarr[] = {
    1,2,3,4,5,6,7,8,9,0
};

int main() {
  TestClass instance;
  printf("instance: %p, carr: %p, iarr: %pn", amp;instance, instance.carr, instance.iarr);
  fflush(stdout);
  while(1);
  return 0;
}
  

Результатом программы является следующее:

 instance: 0xffffcba8, carr: 0x100403020, iarr: 0x100403040
  

Это также подтверждается файлом map. Соответствующая часть:

  .rdata         0x0000000100403000       0xa0 ./src/main.o
                0x0000000100403020                TestClass::carr
                0x0000000100403040                TestClass::iarr
  

Однако gdb показывает это:

 p instance.iarr
$2 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
p amp;instance.iarr
[New Thread 57872.0x4f28]
$3 = (const int (*)[10]) 0x60003b8a0
p amp;instance.iarr
$4 = (const int (*)[10]) 0x60003b8d0
  

И что еще более интересно, этот адрес меняется каждый раз, когда я пытаюсь распечатать его с помощью gdb. В чем причина этого?

Скорректированы заголовок вопроса и теги.

Комментарии:

1. Боюсь, это будет один из тех вопросов с большим количеством голосов и без ответа. Возможно, вы захотите задать вопрос напрямую в списке рассылки gcc, поскольку, боюсь, большинство присутствующих здесь не будут знать об этих довольно загадочных вещах.

2. Почему ваш второй вывод показывает размеры массивов, а первый — нет? Может ли быть, что iarr[1000] слишком велико для ROM и поэтому было помещено в RAM?

3. Пожалуйста, добавьте некоторую информацию об используемой цепочке инструментов и цели, поскольку воспроизвести проблему невозможно

4. @rozina Вот как Eclipse показывает это мне. Для целевого назначения я использую ПЗУ (2 МБ), которое намного больше доступной оперативной памяти (0,5 МБ). Несмотря на это, компоновщик вернет ошибку, если данные не поместятся в ПЗУ.

5. Вопрос @YannDroneaud отредактирован с добавлением дополнительной информации

Ответ №1:

gdb копирует ваш массив в оперативную память, вам даже не нужен экземпляр для него, достаточно класса с vtable:

 (gdb) p TestClass::iarr
$1 = {1, 2, 3, 4, 5, 6}
(gdb) p (int*)TestClass::iarr
$2 = (int *) 0x7ffff7a8b780
(gdb) p *(int *) 0x7ffff7a8b780 @ 100
$3 = {1, 2, 3, 4, 5, 6, 0 <repeats 94 times>}
(gdb) p (int*)TestClass::iarr
$4 = (int *) 0x7ffff7a8b7a0
(gdb) p (int*)TestClass::iarr
$5 = (int *) 0x7ffff7a8b7c0
(gdb) p *(int *) 0x7ffff7a8b780 @ 100
$6 = {1, 2, 3, 4, 5, 6, 0, 0, 1, 2, 3, 4, 5, 6, 0, 0, 1, 2, 3, 4, 5, 6, 0 <repeats 78 times>}
  

Я предполагаю, что это сводится к интерпретации gdb «C». Если вам нужен реальный адрес в gdb, вам нужна функция, которая его возвращает.

Комментарии:

1. Это то, что я также заметил — массив под этим адресом действительно был там. Это где-нибудь задокументировано? Есть ли способ отключить это?