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
помещен в RAM, что совсем не то, что я ожидал.
Этот файл скомпилирован так:
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
- Увеличение массива для превышения некоторого порога, если он есть. Я сделал его размер 10k элементов (времена sizeof (uint32_t)), и он все еще был в оперативной памяти
- Пробуем три разные версии набора инструментов
Toolchain - это arm-none-eabi
. Пробные версии (выходы arm-none-eabi-gcc --version
):
- arm-none-eabi-gcc.exe (инструменты GNU для встроенных процессоров ARM) 4.9.3 20150529 (выпуск) [редакция ARM/embedded-4_9-branch 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)
Первый - с официального сайта ARM: https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads. Последние два взяты из http://www.freddiechopin.info/en/download/category/11-bleeding-edge-toolchain, так как ARM официально не выпускает 64-битную версию, а наш проект вырос до размера, который ломает 32-битную версия.
Почему это проблема и почему я специально изучаю переключение компилятора: возможно, есть другой способ принудительно ввести эти значения в ПЗУ, записав его немного по-другому. Это не вариант - мы столкнулись с этой проблемой недавно в более крупном проекте, охватывающем тысячи файлов, где наследование классов активно используется в разных местах. Перехват всех возможных вхождений таких массивов (некоторые из которых создаются с помощью макросов, некоторые из них внешними инструментами) и последующая реорганизация всего этого кода исключены. Поэтому я ищу причину, по которой компилятор ведет себя именно так, и каковы возможные решения, не связанные с прикосновением к исходным файлам.
РЕДАКТИРОВАТЬ: Кажется, это какая-то проблема с 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: %p\n", &instance, instance.carr, instance.iarr);
fflush(stdout);
while(1);
return 0;
}
Вывод программы такой:
instance: 0xffffcba8, carr: 0x100403020, iarr: 0x100403040
Это также подтверждается файлом карты. Соответствующая часть:
.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 &instance.iarr
[New Thread 57872.0x4f28]
$3 = (const int (*)[10]) 0x60003b8a0
p &instance.iarr
$4 = (const int (*)[10]) 0x60003b8d0
И что еще более интересно, этот адрес меняется каждый раз, когда я пытаюсь напечатать его с помощью gdb. В чем причина этого?
Заголовок вопроса и теги скорректированы.
Ответы
Ответ 1
GDB копирует ваш массив в RAM, вам даже не нужен экземпляр для него, достаточно класса с 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, вам нужна функция, которая его возвращает.