Исследуем работу стека нитей в ChibiOS
kayo — Сб, 25/01/2014 - 13:12
Операционные системы реального времени (RTOS) существенно облегчают разработку качественной программной начинки для микроконтроллеров. Делается это введением некоторых абстракций, таких как нити (потоки исполнения) и примитивы синхронизации (семафоры, мютексы, события, сообщения), потоки ввода/вывода, устройства слоя абстракции аппаратуры (HAL) и другие. В этой статье мы рассмотрим внутреннюю организацию нитей (threads), изолированных потоков исполнения кода, в ChibiOS.
Изучаем устройство нити
Каждая нить в оперативной памяти устройства привязана к так называемой рабочей области нити (TWA). В целях оптимизации рабочая область имеет размер кратный 64 битам, то есть это массив из 64-битных значений. В начале рабочей области лежит собственно структура Thread, поля которой представляют параметры нити (состояние, приоритет, контекст CPU, ссылку на следующую и предыдущую нити в списке и т.д.), а также свойства, отвечающие за работу примитивов синхронизации нитей. Мы можем получить указатель на текущую нить, в которой мы находимся, макросом chThdSelf(). Далее расположена область локального хранилища (LSA), указатель на которую возвращает макрос chThdLS(). Всё остальное место в рабочей области выделятся под стек, который растёт с конца, то есть с вершины рабочей области нити. Все локальные переменные нити и вызываемых в ней функций располагаются в этом стеке. Далее с помощью отладчика мы исследуем как это происходит изнутри, а пока рассмотрим теоретические вопросы.
В ChibiOS имеются средства контроля переполнения стека, которые помогают выявлять проблемы с размером стека в процессе отладки. Включается эта особенность макросом CH_DBG_ENABLE_STACK_CHECK. В структуре нити будет присутствовать поле p_stklimit, содержащее адрес сразу после структуры нити в рабочей области, выравненный по границе 64 бит, как сказано выше. Инициализируется это поле при создании нити, поэтому, если мы хотим использовать область локального хранилища, нам потребуется увеличить значение этого поля на размер наших данных, которые мы намереваемся туда положить. Если этого не сделать, то средства контроля переполнения стека не будут работать должным образом.
Чтобы не быть голословными, давайте приведём небольшой пример того, зачем это нужно и как это можно сделать. Предположим мы создали нить командной оболочки, которая доступна на последовательном порту нашего устройства. Команды представляют собой функции, похожие на функцию main, то есть оболочка разбирает введённые строки подобно оболочке Unix и вызывает команды, передавая им количество и массив аргументов. Однако, внутри команд может потребоваться доступ к различным локальным данным нити оболочки, таким как поток ввода вывода, и сам экземпляр оболочки, если команда намеревается запрашивать или выводить данные, а может быть даже вызывать другие команды оболочки внутри себя. Мы заранее не знаем потребности разных команд, а при любой необходимости передать какие-то новые параметры менять сигнатуру функции команды неправильный подход. Мы можем использовать локальную области нити для хранения указателя на оболочку и внутри команд, если необходимо получать нужные нам локальные переменные через него, например так:
typedef struct { BaseSequentialStream *stdio; } Shell; typedef struct { const char* cmd; int argc; char *argv[SHELL_MAX_ARGS]; } ShellCmd; typedef struct { Shell *sh; ShellCmd *cmd; } ShellLocal; static msg_t shellThread(void *arg){ Shell *sh = arg; #if CH_DBG_ENABLE_STACK_CHECK chThdSelf()->p_stklimit = (stkalign_t*)((void*)chThdSelf()->p_stklimit + sizeof(shellLocal)); #endif ShellLocal *local = chThdLS(); local->sh = sh; /* ... */ for(; /* ... */;){ /* разбор команды */ local->cmd = cmd; /* выполнение команды */ } // ... return 0; } #define stdin (((ShellLocal*)chThdLS())->sh->stdio) #define stdout stdin #define shellCmd() (((ShellLocal*)chThdLS())->cmd) /* ... */ int shell_command_test(int argc, char* argv[]){ ShellCmd *cmd = shellCmd(); chprintf(stdout, "Hello from %s!\r\n", cmd->cmd); return 0; }
Как уже было сказано выше, стек растёт от конца рабочей области к началу, то есть к локальному хранилищу. В целях отладки в ChibiOS предусмотрена такая особенность: стек свежесозданной нити заполняется неким паттерном (например 0x55), чтобы в процессе отладки путём исследования содержимого рабочей области можно было выяснить реально используемый размер стека (наличие паттерна 0x55 будет означать неиспользуемые ячейки памяти). Эта особенность включается макросом CH_DBG_FILL_THREADS.
Когда нить не находится в состоянии выполнения на вершине стека находится ещё одна область, так называемая область вытеснения (PA). Эта область содержит три структуры:
- Внешний контекст (External Context)
- Стек прерывания (Interrupt Stack)
- Внутренний контекст (Internal Context)
Эти структуры кладутся в стек при вытеснении выполняющейся в данный момент нити обработчиками прерываний и переключением контекста на другую нить.
Для вычисления размера рабочей области служит макрос THD_WA_SIZE(size), которому передаётся требуемый размер стека. К нему прибавляется размер структуры нити и области вытеснения. Последний факт надо иметь ввиду при анализе используемого размера стека, ведь вытеснения может и не успеть произойти к моменту, когда мы анализируем заполнение по наличию паттерна отладчиком.
Исследуем работу стека
Для получения практического опыта напишем простейшую программу под ChibiOS, содержащую всего одну нить кроме главной, и будем смотреть что в ней происходит средствами отладчика. Итак, вот код программы:
#include "ch.h" #include "hal.h" static WORKING_AREA(waTest, 128); static int test1(int32_t i32, int16_t i16, uint8_t u8){ /* функция, вызванная внутри нити */ int64_t i64 = (i32 << (16 + 8)) + (i16 << 8) + u8; uint32_t u32 = 0x33223322; uint16_t u16 = 0x1166; int8_t i8 = 0x88; return (i64 << (32 + 16 + 8)) | (u32 << (16 + 8)) | (u16 << 8) | i8; } static msg_t Test(void *arg) { /* наша нить */ (void)arg; int32_t i32 = 0x32323232; int16_t i16 = 0x1616; uint8_t u8 = 0x08; /* запускаем функцию внутри нити */ uint64_t u64 = test1(i32, i16, u8); return u64; } int main(void) { halInit(); chSysInit(); /* стартуем нить */ Thread *thd = chThdCreateStatic(waTest, sizeof(waTest), NORMALPRIO, Test, NULL); /* ждём завершения нити */ chThdWait(thd); return 0; }
В этом примере нам потребуется отключить оптимизацию, потому что мнение GCC тулчейна далеко от того, чего мы с вами хотим получить. Можете взять конфиги и Makefile из какого-нибудь примера под вашу платформу. Поскольку я использую Cortex-M3 STM32F1xx, то во вложении к посту будет пример под него. Прописываем в Makefile в переменную CHIBIOS путь к вашей копии ChibiOS, также не забываем указать корректные BOARD и LDSCRIPT, соответствующие вашему устройству.
Я также написал скрипты для OpenOCD (board.cfg и flash.cfg) правила flash и debug, и скрипт инициализации отладчика GDB (.gdbinit). Итак приступим:
# Собираем $ make # Присоединёем отладочный интерфейс и шьём $ make flash # Запускаем интерфейс отладки OpenOCD $ make debug # Стартуем отладчик GDB $ arm-none-eabi-gdb
Отладчик лучше всего запускать внутри вашей среды разработки. В GNU Emacs присутствует обёртка для GDB, которая запускается командой M-x gdb. Не забываем указать корректный отладчик, как в примере выше. Если в вашем тулчейне запрещено выполнять скрипты инициализации GDB, необходимо их разрешить или указать скрипт/выполнить его вручную:
# подсоединяемся к OpenOCD target remote localhost:3333 # загружаем файл программы file build/ch.elf # делаем сброс monitor reset halt # ставим точку останова в функцию нити break Test # продолжаем выполнение программы на устройстве continue
Мы попадаем в главную функцию нашей только что созданной нити и можем посмотреть содержимое рабочей области:
# для начала узнаем размер рабочей области в байтах (gdb) p sizeof(waTest) $1 = 304 # теперь смотрим всё содержимое рабочей области (gdb) x/304xb waTest 0x20000950 <waTest>: 0x00 0x08 0x00 0x20 0x24 0x09 0x00 0x20 0x20000958 <waTest+8>: 0x40 0x00 0x00 0x00 0x5c 0x0a 0x00 0x20 0x20000960 <waTest+16>: 0x24 0x09 0x00 0x20 0x00 0x08 0x00 0x20 0x20000968 <waTest+24>: 0x00 0x00 0x00 0x00 0x98 0x09 0x00 0x20 0x20000970 <waTest+32>: 0x01 0x00 0xff 0xff 0x00 0x00 0x00 0x00 0x20000978 <waTest+40>: 0x00 0x00 0x00 0x00 0xc0 0x08 0x00 0x20 0x20000980 <waTest+48>: 0x80 0x09 0x00 0x20 0x80 0x09 0x00 0x20 0x20000988 <waTest+56>: 0xff 0xff 0xff 0xff 0x00 0x00 0x00 0x00 0x20000990 <waTest+64>: 0x00 0x00 0x00 0x00 0x40 0x00 0x00 0x00 0x20000998 <waTest+72>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009a0 <waTest+80>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009a8 <waTest+88>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009b0 <waTest+96>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009b8 <waTest+104>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009c0 <waTest+112>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009c8 <waTest+120>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009d0 <waTest+128>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009d8 <waTest+136>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009e0 <waTest+144>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009e8 <waTest+152>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009f0 <waTest+160>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009f8 <waTest+168>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a00 <waTest+176>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a08 <waTest+184>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a10 <waTest+192>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a18 <waTest+200>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a20 <waTest+208>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a28 <waTest+216>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a30 <waTest+224>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a38 <waTest+232>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a40 <waTest+240>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a48 <waTest+248>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a50 <waTest+256>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a58 <waTest+264>: 0x55 0x55 0x55 0x55 0x00 0x00 0x00 0x00 0x20000a60 <waTest+272>: 0x00 0x00 0x00 0x00 0x55 0x55 0x55 0x55 0x20000a68 <waTest+280>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a70 <waTest+288>: 0x50 0x09 0x00 0x20 0x4f 0x03 0x00 0x08 0x20000a78 <waTest+296>: 0x50 0x09 0x00 0x20 0x75 0x08 0x00 0x08 (gdb)
Видим, что по крайней мере 36 байт с конца (от waTest+268 до waTest+304) уже не имеют паттерн 0x55, а значит были использованы до входа в главную функцию нити. Но это не значит, что вершина стека в данный момент находится на границе паттерна (то есть waTest+268), вероятно, она ближе к концу рабочей области, в чём мы можем удостовериться:
# мы остановились на строчке int32_t i32 = 0x32323232; # здесь инициализируется первая локальная переменная # смотрим, где её разместил в стеке компилятор (gdb) p &i32 $2 = (int32_t *) 0x20000a6c <waTest+284> (gdb)
Теперь пойдём по строчкам и будем смотреть, как меняется содержимое стека:
(gdb) p &i32 $2 = (int32_t *) 0x20000a6c <waTest+284> (gdb) n 19 int16_t i16 = 0x1616; (gdb) x/304xb waTest 0x20000950 <waTest>: 0x00 0x08 0x00 0x20 0x24 0x09 0x00 0x20 0x20000958 <waTest+8>: 0x40 0x00 0x00 0x00 0x5c 0x0a 0x00 0x20 0x20000960 <waTest+16>: 0x24 0x09 0x00 0x20 0x00 0x08 0x00 0x20 0x20000968 <waTest+24>: 0x00 0x00 0x00 0x00 0x98 0x09 0x00 0x20 0x20000970 <waTest+32>: 0x01 0x00 0xff 0xff 0x00 0x00 0x00 0x00 0x20000978 <waTest+40>: 0x00 0x00 0x00 0x00 0xc0 0x08 0x00 0x20 0x20000980 <waTest+48>: 0x80 0x09 0x00 0x20 0x80 0x09 0x00 0x20 0x20000988 <waTest+56>: 0xff 0xff 0xff 0xff 0x00 0x00 0x00 0x00 0x20000990 <waTest+64>: 0x00 0x00 0x00 0x00 0x40 0x00 0x00 0x00 0x20000998 <waTest+72>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009a0 <waTest+80>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009a8 <waTest+88>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009b0 <waTest+96>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009b8 <waTest+104>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009c0 <waTest+112>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009c8 <waTest+120>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009d0 <waTest+128>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009d8 <waTest+136>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009e0 <waTest+144>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009e8 <waTest+152>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009f0 <waTest+160>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009f8 <waTest+168>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a00 <waTest+176>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a08 <waTest+184>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a10 <waTest+192>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a18 <waTest+200>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a20 <waTest+208>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a28 <waTest+216>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a30 <waTest+224>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a38 <waTest+232>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a40 <waTest+240>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a48 <waTest+248>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a50 <waTest+256>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a58 <waTest+264>: 0x55 0x55 0x55 0x55 0x00 0x00 0x00 0x00 0x20000a60 <waTest+272>: 0x00 0x00 0x00 0x00 0x55 0x55 0x55 0x55 0x20000a68 <waTest+280>: 0x55 0x55 0x55 0x55 0x32 0x32 0x32 0x32 0x20000a70 <waTest+288>: 0x50 0x09 0x00 0x20 0x4f 0x03 0x00 0x08 0x20000a78 <waTest+296>: 0x50 0x09 0x00 0x20 0x75 0x08 0x00 0x08 (gdb) p &i16 $3 = (int16_t *) 0x20000a6a <waTest+282> (gdb) n 20 uint8_t u8 = 0x08; (gdb) x/304xb waTest 0x20000950 <waTest>: 0x00 0x08 0x00 0x20 0x24 0x09 0x00 0x20 0x20000958 <waTest+8>: 0x40 0x00 0x00 0x00 0x5c 0x0a 0x00 0x20 0x20000960 <waTest+16>: 0x24 0x09 0x00 0x20 0x00 0x08 0x00 0x20 0x20000968 <waTest+24>: 0x00 0x00 0x00 0x00 0x98 0x09 0x00 0x20 0x20000970 <waTest+32>: 0x01 0x00 0xff 0xff 0x00 0x00 0x00 0x00 0x20000978 <waTest+40>: 0x00 0x00 0x00 0x00 0xc0 0x08 0x00 0x20 0x20000980 <waTest+48>: 0x80 0x09 0x00 0x20 0x80 0x09 0x00 0x20 0x20000988 <waTest+56>: 0xff 0xff 0xff 0xff 0x00 0x00 0x00 0x00 0x20000990 <waTest+64>: 0x00 0x00 0x00 0x00 0x40 0x00 0x00 0x00 0x20000998 <waTest+72>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009a0 <waTest+80>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009a8 <waTest+88>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009b0 <waTest+96>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009b8 <waTest+104>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009c0 <waTest+112>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009c8 <waTest+120>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009d0 <waTest+128>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009d8 <waTest+136>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009e0 <waTest+144>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009e8 <waTest+152>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009f0 <waTest+160>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009f8 <waTest+168>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a00 <waTest+176>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a08 <waTest+184>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a10 <waTest+192>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a18 <waTest+200>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a20 <waTest+208>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a28 <waTest+216>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a30 <waTest+224>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a38 <waTest+232>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a40 <waTest+240>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a48 <waTest+248>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a50 <waTest+256>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a58 <waTest+264>: 0x55 0x55 0x55 0x55 0x00 0x00 0x00 0x00 0x20000a60 <waTest+272>: 0x00 0x00 0x00 0x00 0x55 0x55 0x55 0x55 0x20000a68 <waTest+280>: 0x55 0x55 0x16 0x16 0x32 0x32 0x32 0x32 0x20000a70 <waTest+288>: 0x50 0x09 0x00 0x20 0x4f 0x03 0x00 0x08 0x20000a78 <waTest+296>: 0x50 0x09 0x00 0x20 0x75 0x08 0x00 0x08 (gdb) n 22 uint64_t u64 = test1(i32, i16, u8);
(gdb) p &u8 $2 = (uint8_t *) 0x20000a69 <waTest+281> (gdb) x/304xb waTest 0x20000950 <waTest>: 0x00 0x08 0x00 0x20 0x24 0x09 0x00 0x20 0x20000958 <waTest+8>: 0x40 0x00 0x00 0x00 0x5c 0x0a 0x00 0x20 0x20000960 <waTest+16>: 0x24 0x09 0x00 0x20 0x00 0x08 0x00 0x20 0x20000968 <waTest+24>: 0x00 0x00 0x00 0x00 0x98 0x09 0x00 0x20 0x20000970 <waTest+32>: 0x01 0x00 0xff 0xff 0x00 0x00 0x00 0x00 0x20000978 <waTest+40>: 0x00 0x00 0x00 0x00 0xc0 0x08 0x00 0x20 0x20000980 <waTest+48>: 0x80 0x09 0x00 0x20 0x80 0x09 0x00 0x20 0x20000988 <waTest+56>: 0xff 0xff 0xff 0xff 0x00 0x00 0x00 0x00 0x20000990 <waTest+64>: 0x00 0x00 0x00 0x00 0x40 0x00 0x00 0x00 0x20000998 <waTest+72>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009a0 <waTest+80>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009a8 <waTest+88>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009b0 <waTest+96>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009b8 <waTest+104>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009c0 <waTest+112>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009c8 <waTest+120>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009d0 <waTest+128>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009d8 <waTest+136>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009e0 <waTest+144>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009e8 <waTest+152>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009f0 <waTest+160>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009f8 <waTest+168>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a00 <waTest+176>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a08 <waTest+184>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a10 <waTest+192>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a18 <waTest+200>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a20 <waTest+208>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a28 <waTest+216>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a30 <waTest+224>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a38 <waTest+232>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a40 <waTest+240>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a48 <waTest+248>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a50 <waTest+256>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a58 <waTest+264>: 0x55 0x55 0x55 0x55 0x00 0x00 0x00 0x00 0x20000a60 <waTest+272>: 0x00 0x00 0x00 0x00 0x55 0x55 0x55 0x55 0x20000a68 <waTest+280>: 0x55 0x08 0x16 0x16 0x32 0x32 0x32 0x32 0x20000a70 <waTest+288>: 0x50 0x09 0x00 0x20 0x4f 0x03 0x00 0x08 0x20000a78 <waTest+296>: 0x50 0x09 0x00 0x20 0x75 0x08 0x00 0x08
(gdb) p &u64 $1 = (uint64_t *) 0x20000a60 <waTest+272>
(gdb) x/304xb waTest 0x20000950 <waTest>: 0x00 0x08 0x00 0x20 0x24 0x09 0x00 0x20 0x20000958 <waTest+8>: 0x40 0x00 0x00 0x00 0x5c 0x0a 0x00 0x20 0x20000960 <waTest+16>: 0x24 0x09 0x00 0x20 0x00 0x08 0x00 0x20 0x20000968 <waTest+24>: 0x00 0x00 0x00 0x00 0x98 0x09 0x00 0x20 0x20000970 <waTest+32>: 0x01 0x00 0xff 0xff 0x00 0x00 0x00 0x00 0x20000978 <waTest+40>: 0x00 0x00 0x00 0x00 0xc0 0x08 0x00 0x20 0x20000980 <waTest+48>: 0x80 0x09 0x00 0x20 0x80 0x09 0x00 0x20 0x20000988 <waTest+56>: 0xff 0xff 0xff 0xff 0x00 0x00 0x00 0x00 0x20000990 <waTest+64>: 0x00 0x00 0x00 0x00 0x40 0x00 0x00 0x00 0x20000998 <waTest+72>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009a0 <waTest+80>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009a8 <waTest+88>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009b0 <waTest+96>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009b8 <waTest+104>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009c0 <waTest+112>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009c8 <waTest+120>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009d0 <waTest+128>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009d8 <waTest+136>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009e0 <waTest+144>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009e8 <waTest+152>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009f0 <waTest+160>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009f8 <waTest+168>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a00 <waTest+176>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a08 <waTest+184>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a10 <waTest+192>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a18 <waTest+200>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a20 <waTest+208>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a28 <waTest+216>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a30 <waTest+224>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a38 <waTest+232>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a40 <waTest+240>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a48 <waTest+248>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a50 <waTest+256>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a58 <waTest+264>: 0x55 0x55 0x55 0x55 0x00 0x00 0x00 0x00 0x20000a60 <waTest+272>: 0x00 0x00 0x00 0x00 0x55 0x55 0x55 0x55 0x20000a68 <waTest+280>: 0x55 0x08 0x16 0x16 0x32 0x32 0x32 0x32 0x20000a70 <waTest+288>: 0x50 0x09 0x00 0x20 0x4f 0x03 0x00 0x08 0x20000a78 <waTest+296>: 0x50 0x09 0x00 0x20 0x75 0x08 0x00 0x08 (gdb)
Видим как меняется содержимое областей, соответствующих адресам переменных (выделено жирным). Переменные расположены в стеке без выравнивания, что соответствует архитектуре Cortex-M. Теперь зайдём в функцию test1, и посмотрим, что произошло со стеком:
(gdb) s test1 (i32=842150450, i16=5654, u8=8 '\b') at main.c:7 7 int64_t i64 = (i32 << (16 + 8)) + (i16 << 8) + u8; (gdb) x/304xb waTest 0x20000950 <waTest>: 0x00 0x08 0x00 0x20 0x24 0x09 0x00 0x20 0x20000958 <waTest+8>: 0x40 0x00 0x00 0x00 0x5c 0x0a 0x00 0x20 0x20000960 <waTest+16>: 0x24 0x09 0x00 0x20 0x00 0x08 0x00 0x20 0x20000968 <waTest+24>: 0x00 0x00 0x00 0x00 0x98 0x09 0x00 0x20 0x20000970 <waTest+32>: 0x01 0x00 0xff 0xff 0x00 0x00 0x00 0x00 0x20000978 <waTest+40>: 0x00 0x00 0x00 0x00 0xc0 0x08 0x00 0x20 0x20000980 <waTest+48>: 0x80 0x09 0x00 0x20 0x80 0x09 0x00 0x20 0x20000988 <waTest+56>: 0xff 0xff 0xff 0xff 0x00 0x00 0x00 0x00 0x20000990 <waTest+64>: 0x00 0x00 0x00 0x00 0x40 0x00 0x00 0x00 0x20000998 <waTest+72>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009a0 <waTest+80>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009a8 <waTest+88>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009b0 <waTest+96>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009b8 <waTest+104>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009c0 <waTest+112>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009c8 <waTest+120>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009d0 <waTest+128>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009d8 <waTest+136>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009e0 <waTest+144>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009e8 <waTest+152>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009f0 <waTest+160>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009f8 <waTest+168>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a00 <waTest+176>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a08 <waTest+184>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a10 <waTest+192>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a18 <waTest+200>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a20 <waTest+208>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a28 <waTest+216>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a30 <waTest+224>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a38 <waTest+232>: 0x55 0x08 0x16 0x16 0x32 0x32 0x32 0x32 0x20000a40 <waTest+240>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a48 <waTest+248>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a50 <waTest+256>: 0x21 0x10 0x00 0x08 0x00 0x00 0x00 0x00 0x20000a58 <waTest+264>: 0x55 0x55 0x55 0x55 0x00 0x00 0x00 0x00 0x20000a60 <waTest+272>: 0x00 0x00 0x00 0x00 0x55 0x55 0x55 0x55 0x20000a68 <waTest+280>: 0x55 0x08 0x16 0x16 0x32 0x32 0x32 0x32 0x20000a70 <waTest+288>: 0x50 0x09 0x00 0x20 0x4f 0x03 0x00 0x08 0x20000a78 <waTest+296>: 0x50 0x09 0x00 0x20 0x75 0x08 0x00 0x08 (gdb)
Можно заметить копию наших переменных в области немного выше в стеке. Как нетрудно догадаться, это переданные нами параметры функции, которые предусмотрительно скопировал компилятор:
(gdb) p &i32
$1 = (int32_t *) 0x20000a3c <waTest+236> p &i16 $3 = (int16_t *) 0x20000a3a <waTest+234> (gdb) p &u8 $4 = (uint8_t *) 0x20000a39 <waTest+233> (gdb)
Продвинемся ещё на 4 шага вперёд, к моменту возврата значения из функции, и посмотрим, что изменилось:
(gdb) n 4 12 return (i64 << (32 + 16 + 8)) | (u32 << (16 + 8)) | (u16 << 8) | i8; (gdb) x/304xb waTest 0x20000950 <waTest>: 0x00 0x08 0x00 0x20 0x24 0x09 0x00 0x20 0x20000958 <waTest+8>: 0x40 0x00 0x00 0x00 0x5c 0x0a 0x00 0x20 0x20000960 <waTest+16>: 0x24 0x09 0x00 0x20 0x00 0x08 0x00 0x20 0x20000968 <waTest+24>: 0x00 0x00 0x00 0x00 0x98 0x09 0x00 0x20 0x20000970 <waTest+32>: 0x01 0x00 0xff 0xff 0x00 0x00 0x00 0x00 0x20000978 <waTest+40>: 0x00 0x00 0x00 0x00 0xc0 0x08 0x00 0x20 0x20000980 <waTest+48>: 0x80 0x09 0x00 0x20 0x80 0x09 0x00 0x20 0x20000988 <waTest+56>: 0xff 0xff 0xff 0xff 0x00 0x00 0x00 0x00 0x20000990 <waTest+64>: 0x00 0x00 0x00 0x00 0x40 0x00 0x00 0x00 0x20000998 <waTest+72>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009a0 <waTest+80>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009a8 <waTest+88>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009b0 <waTest+96>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009b8 <waTest+104>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009c0 <waTest+112>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009c8 <waTest+120>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009d0 <waTest+128>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009d8 <waTest+136>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009e0 <waTest+144>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009e8 <waTest+152>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009f0 <waTest+160>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009f8 <waTest+168>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a00 <waTest+176>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a08 <waTest+184>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a10 <waTest+192>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a18 <waTest+200>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a20 <waTest+208>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a28 <waTest+216>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a30 <waTest+224>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a38 <waTest+232>: 0x55 0x08 0x16 0x16 0x32 0x32 0x32 0x32 0x20000a40 <waTest+240>: 0x55 0x88 0x66 0x11 0x22 0x33 0x22 0x33 0x20000a48 <waTest+248>: 0x08 0x16 0x16 0x32 0x00 0x00 0x00 0x00 0x20000a50 <waTest+256>: 0x21 0x10 0x00 0x08 0x00 0x00 0x00 0x00 0x20000a58 <waTest+264>: 0x55 0x55 0x55 0x55 0x00 0x00 0x00 0x00 0x20000a60 <waTest+272>: 0x00 0x00 0x00 0x00 0x55 0x55 0x55 0x55 0x20000a68 <waTest+280>: 0x55 0x08 0x16 0x16 0x32 0x32 0x32 0x32 0x20000a70 <waTest+288>: 0x50 0x09 0x00 0x20 0x4f 0x03 0x00 0x08 0x20000a78 <waTest+296>: 0x50 0x09 0x00 0x20 0x75 0x08 0x00 0x08 (gdb) p &i64 $5 = (int64_t *) 0x20000a48 <waTest+248> (gdb) p &u32 $6 = (uint32_t *) 0x20000a44 <waTest+244> (gdb) p &u16 $7 = (uint16_t *) 0x20000a42 <waTest+242> (gdb) p &i8 $8 = (int8_t *) 0x20000a41 <waTest+241>
Как видим, локальные переменные функции расположены глубже в стеке, чем аргументы, переданные в функцию. Я намеренно допустил здесь небрежность в вычислениях (пренебрёг приведением типов), чтобы вы увидели, что происходит со значениями. Теперь мы шагнём уже за возврат из функции.
(gdb) s Test (arg=0x0) at main.c:24 24 return u64; (gdb) x/304xb waTest 0x20000950 <waTest>: 0x00 0x08 0x00 0x20 0x24 0x09 0x00 0x20 0x20000958 <waTest+8>: 0x40 0x00 0x00 0x00 0x5c 0x0a 0x00 0x20 0x20000960 <waTest+16>: 0x24 0x09 0x00 0x20 0x00 0x08 0x00 0x20 0x20000968 <waTest+24>: 0x00 0x00 0x00 0x00 0x98 0x09 0x00 0x20 0x20000970 <waTest+32>: 0x01 0x00 0xff 0xff 0x00 0x00 0x00 0x00 0x20000978 <waTest+40>: 0x00 0x00 0x00 0x00 0xc0 0x08 0x00 0x20 0x20000980 <waTest+48>: 0x80 0x09 0x00 0x20 0x80 0x09 0x00 0x20 0x20000988 <waTest+56>: 0xff 0xff 0xff 0xff 0x00 0x00 0x00 0x00 0x20000990 <waTest+64>: 0x00 0x00 0x00 0x00 0x40 0x00 0x00 0x00 0x20000998 <waTest+72>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009a0 <waTest+80>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009a8 <waTest+88>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009b0 <waTest+96>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009b8 <waTest+104>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009c0 <waTest+112>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009c8 <waTest+120>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009d0 <waTest+128>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009d8 <waTest+136>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009e0 <waTest+144>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009e8 <waTest+152>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009f0 <waTest+160>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009f8 <waTest+168>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a00 <waTest+176>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a08 <waTest+184>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a10 <waTest+192>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a18 <waTest+200>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a20 <waTest+208>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a28 <waTest+216>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a30 <waTest+224>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a38 <waTest+232>: 0x55 0x08 0x16 0x16 0x32 0x32 0x32 0x32 0x20000a40 <waTest+240>: 0x55 0x88 0x66 0x11 0x22 0x33 0x22 0x33 0x20000a48 <waTest+248>: 0x08 0x16 0x16 0x32 0x00 0x00 0x00 0x00 0x20000a50 <waTest+256>: 0x21 0x10 0x00 0x08 0x00 0x00 0x00 0x00 0x20000a58 <waTest+264>: 0x55 0x55 0x55 0x55 0x00 0x00 0x00 0x00 0x20000a60 <waTest+272>: 0x88 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0x20000a68 <waTest+280>: 0x55 0x08 0x16 0x16 0x32 0x32 0x32 0x32 0x20000a70 <waTest+288>: 0x50 0x09 0x00 0x20 0x4f 0x03 0x00 0x08 0x20000a78 <waTest+296>: 0x50 0x09 0x00 0x20 0x75 0x08 0x00 0x08 (gdb) p &u64 $9 = (uint64_t *) 0x20000a60 <waTest+272> (gdb)
Значение вернулось в переменную, лежащую выше предыдущих локальных переменных в стеке, причем расположена она с выравниванием на её размер, то есть 64 бита.
Забуриваемся глубже
Осталось разобраться, что же лежит в стеке между локальными переменными вызывающей функции и вызываемой. Для этого нам потребуется «вооружиться линзой», то есть открыть буфер дизассемблера и идти далее не по шагам, а по инструкциям CPU. Итак, перезапускаем пример, и встаём прямо перед вызовом функции test1, далее мы будем идти по инструкциям командой si.
…
0x0800101a <Test+26>: ldrsh.w r2, [sp, #18] 0x0800101e <Test+30>: ldrb.w r3, [sp, #17] 0x08001022 <Test+34>: ldr r0, [sp, #20] 0x08001024 <Test+36>: mov r1, r2 0x08001026 <Test+38>: mov r2, r3 0x08001028 <Test+40>: bl 0x8000fb0 <test1>
Первые три инструкции (ldr*) загружают значения аргументов функции из стека в регистры (для каждого размера типа используется свой вариант инструкции). Далее две инструкции (mov) перемещают значения регистров так, чтобы параметры функции располагались в r2, r1, r0 соответственно. Потом происходит собственно вызов функции (bl). Далее мы попадаем уже внутрь функции:
0x08000fb0 <test1+0>: push {r4, r5} 0x08000fb2 <test1+2>: sub sp, #24 0x08000fb4 <test1+4>: str r0, [sp, #4] 0x08000fb6 <test1+6>: mov r3, r2 0x08000fb8 <test1+8>: mov r2, r1 0x08000fba <test1+10>: strh.w r2, [sp, #2] 0x08000fbe <test1+14>: strb.w r3, [sp, #1]
…
Посмотрим текущее значение указателя стека во фрэйме регистров, у меня это 0x20000a58, именно сюда первая команда push поместит значения регистров r4, r5. После инструкции push указатель стека сместился назад на 8 байт и принял значение 0x20000a50, а вот что мы имеем в стеке:
(gdb) x/304xb waTest 0x20000950 <waTest>: 0x00 0x08 0x00 0x20 0x24 0x09 0x00 0x20 0x20000958 <waTest+8>: 0x40 0x00 0x00 0x00 0x5c 0x0a 0x00 0x20 0x20000960 <waTest+16>: 0x24 0x09 0x00 0x20 0x00 0x08 0x00 0x20 0x20000968 <waTest+24>: 0x00 0x00 0x00 0x00 0x98 0x09 0x00 0x20 0x20000970 <waTest+32>: 0x01 0x00 0xff 0xff 0x00 0x00 0x00 0x00 0x20000978 <waTest+40>: 0x00 0x00 0x00 0x00 0xc0 0x08 0x00 0x20 0x20000980 <waTest+48>: 0x80 0x09 0x00 0x20 0x80 0x09 0x00 0x20 0x20000988 <waTest+56>: 0xff 0xff 0xff 0xff 0x00 0x00 0x00 0x00 0x20000990 <waTest+64>: 0x00 0x00 0x00 0x00 0x40 0x00 0x00 0x00 0x20000998 <waTest+72>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009a0 <waTest+80>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009a8 <waTest+88>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009b0 <waTest+96>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009b8 <waTest+104>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009c0 <waTest+112>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009c8 <waTest+120>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009d0 <waTest+128>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009d8 <waTest+136>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009e0 <waTest+144>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009e8 <waTest+152>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009f0 <waTest+160>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x200009f8 <waTest+168>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a00 <waTest+176>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a08 <waTest+184>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a10 <waTest+192>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a18 <waTest+200>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a20 <waTest+208>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a28 <waTest+216>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a30 <waTest+224>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a38 <waTest+232>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a40 <waTest+240>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a48 <waTest+248>: 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x20000a50 <waTest+256>: 0x21 0x10 0x00 0x08 0x00 0x00 0x00 0x00 0x20000a58 <waTest+264>: 0x55 0x55 0x55 0x55 0x00 0x00 0x00 0x00 0x20000a60 <waTest+272>: 0x00 0x00 0x00 0x00 0x55 0x55 0x55 0x55 0x20000a68 <waTest+280>: 0x55 0x08 0x16 0x16 0x32 0x32 0x32 0x32 0x20000a70 <waTest+288>: 0x50 0x09 0x00 0x20 0x4f 0x03 0x00 0x08 0x20000a78 <waTest+296>: 0x50 0x09 0x00 0x20 0x75 0x08 0x00 0x08 (gdb)
Вначале мы видим ничто иное как адрес 0x08001021 соответствующий адресу вызвавшей функции (на самом деле он смещён на +1 байт ввиду особенностей архитектуры). Он сохраняется, чтобы после возврата из функции корректно восстановить контекст вызвавшей функции. Следующая инструкция (sub) смещает указатель стека назад на 24, чтобы сохранить значения переменных из регистров и работать с локальными переменными в этом пространстве. Далее инструкции str* сохраняют значения соответственно размерам типов, а перед ними выполняются инструкции mov, перемещающие значения регистров обратно (см. выше). И наконец посмотрим, как происходит возврат из функции:
…
0x08001010 <test1+96>: add sp, #24 0x08001012 <test1+98>: pop {r4, r5} 0x08001014 <test1+100>: bx lr
…
Значение указателя стека возвращается в состояние, которое было в начале функции (помним, тогда вычиталось значение 24, теперь прибавляется оно же). Следующая инструкция (pop) выталкивает из стека ранее сохранённые значения регистров r4, r5. Затем происходит переход по адресу (bx), который содержится в регистре lr. Может возникнуть закономерный вопрос, откуда взялось нужное значение в этом регистре. Документации по инструкциям набора Thumb сообщает, что инструкции переходов (например наша bl, которой был осуществлён вызов функции test1) автоматически пишут адрес следующей инструкции (опять же смещённый на +1 байт ввиду особенностей архитектуры) в регистр связи LR.
В нашем случае была намеренно отключена оптимизация, поэтому мы наблюдаем огромное количество оверхеда. В боевом коде с включенной оптимизацией, компилятор сократит количество инструкций основываясь на различных правилах и предположениях. Наш тривиальный пример через призму оптимизатора будет выглядеть совсем не так, как сейчас и нам будет гораздо труднее понять сию механику особенно на уровне инструкций.
Надеюсь, было интересно. Описание инструкций процессоров ARM находится на официальном сайте. Исследуйте наздоровье, много нового узнаете, я гарантирую это.
Вложение | Размер |
---|---|
test.tar.xz | 7.36 КБ |

Отправить комментарий