Адрес локальной переменной дает неверный адрес

Я столкнулся с ошибкой в ​​своем коде, из-за которой адрес локальной переменной стека является недействительным. Я пытался отладить это с помощью lldb.

В приглашении lldb ниже вы можете видеть, что &dominance_frontier указывает адрес. 0x0000000000000001, что затем вызывает сигнал SIGSEGV на hash_table_init линии вызова 121. Однако &dominator_tree_adj указывает действительный адрес. Я совершенно сбит с толку почему это может быть так.

Process 70998 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = step over
    frame #0: 0x0000000100006fd0 ir`ComputeDominanceFrontier(function=0x00006000030f8000) at dominators.c:121:9
   111          struct Array postorder_traversal = postorder (function->entry_basic_block);
   112          struct DFAConfiguration config = DominatorDFAConfiguration (function);
   113          struct DFAResult result = run_DFA (&config, function);
   114  
   115          HashTable dominator_tree_adj = ComputeDominatorTree (function, &result);
   116          printf("%p\n", &dominator_tree_adj);
   117          HashTable dominance_frontier;
   118          printf("%ld\n", sizeof(dominance_frontier));
   119  
   120  
-> 121          hash_table_init (&dominance_frontier);
   122          // Compute the transpose graph from the dominator tree adjacency list
   123          // Each node is guaranteed to have only one direct predecessor, since
   124          // each node can only have one immediate dominator. We will need this
   125          // in the DF algorithm below
   126          HashTable dominator_tree_transpose;
   127          hash_table_init (&dominator_tree_transpose);
   128  
   129          struct HashTableEntry *entry;
   130          size_t entry_iter = 0;
   131  
Target 0: (ir) stopped.
(lldb) p &dominance_frontier
(HashTable *) 0x0000000000000001
(lldb) p &dominator_tree_adj
(HashTable *) 0x000000016fdfef38

Я компилировал свой код с помощью make DEBUG=yes

OPT = -O3
FLAGS = -Wall -Wextra
CC = cc
OBJECTS =   ir_parser.o \
            main.o \
            threeaddr_parser.o \
            instruction.o \
            function.o \
            basicblock.o \
            constant.o \
            utils.o \
            value.o \
            array.o \
            mem.o \
            map.o \
            dfa.o \
            dominators.o


ifdef DEBUG
    OPT = -g
else
    OPT = -O3
endif


all: $(OBJECTS)
    $(CC) $(OPT) $(FLAGS) $^ -o ir

%.o: %.c *.h
    $(CC) $(OPT) $(FLAGS) -c $< -o $@


clean:
    rm *.o

Вот место сегфолта:

Process 10619 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x9)
    frame #0: 0x0000000100005060 ir`_hash_table_init(table=0x0000000000000001, size=10) at map.c:21:21
   11   // Open addressing, linear probe hash table.
   12   
   13   unsigned long uint64_t_hash_function (uint64_t key)
   14   {
   15           // Simple hash function for demonstration
   16           return key;
   17   }
   18   
   19   void _hash_table_init (HashTable *table, size_t size)
   20   {
-> 21           table->size = size;
   22           table->count = 0;
   23           table->buckets = ir_calloc (table->size, sizeof (HashTableEntry));
   24   }
   25   void hash_table_init (HashTable *table)
   26   {
   27           _hash_table_init (table, MAP_INIT_SIZE_CNT);
   28   }
   29   
   30   // Create a new hash table
   31   HashTable *hash_table_create (size_t size)
Target 0: (ir) stopped.

Входной файл:

fn test4(%1, %2) {

    alloca %9, 5
    add %3, %1, %2 
    store %9, %3
    cmp %4, %1, %2 
    jumpif 1, %4


    sub %5, %3, 1  
    jumpif 2, %5   

1:
    add %6, %1, 20 
    jump 3    


2:
    add %7, %2, 30 

    
3:
    sub %8, %3, %3 
}
./ir -f path/to/input/file

Я использую Apple clang версии 15.0.0 (clang-1500.3.9.4).

Полный код здесь: https://github.com/CoconutJJ/compiler-optimization/blob/d3244e8c9f96e8180c924533abf8f5daac15238c/ir/dominators.c#L115

Пожалуйста, разместите здесь минимально воспроизводимый пример, а не ссылку на удаленный сайт.

Barmar 24.06.2024 00:17

Запустите свой код через valgrind. Если вы неправильно управляете памятью, она подскажет вам, где именно.

dbush 24.06.2024 00:21

Если вы поместите точку останова внутри hash_table_init, вы увидите то же значение? Где на самом деле происходит ошибка segfault (вывод lldb, похоже, остановлен из-за одношагового режима, а не на реальном сайте SIGSEGV)? Наблюдаете ли вы такое же поведение с gdb? Какая это версия компилятора и архитектура?

Nate Eldredge 24.06.2024 00:36

@NateEldredge Я отредактировал вопрос, включив в него версию компилятора. У меня Apple Silicon M1. Я вижу то же значение в hash_table_init. Запуск этого докер-контейнера Ubuntu и использование вместо него gdb отображает segfault в строке возврата функции вместо этого в ComputeDominanceFrontier .

David Yue 24.06.2024 00:46

Вы смотрели дизассемблирование рассматриваемой функции? Если она действительно намеренно передает 1 вместо допустимого адреса стека, что было бы ошибкой компилятора, то ассемблер этой функции должен быть явно неправильным.

Nate Eldredge 24.06.2024 00:51

Нам также понадобится входной файл.

Nate Eldredge 24.06.2024 00:53

@dbush Запуск этого с помощью valgrind внутри док-контейнера Ubuntu с valgrind --leak-check=yes дает «Недопустимая запись размера 8». Но это не кажется очень полезным, поскольку речь идет просто о недействительном адресе, который я уже знаю.

David Yue 24.06.2024 00:54

@NateEldredge Я отредактировал вопрос, включив в него входной файл и команду запуска.

David Yue 24.06.2024 00:55

@NateEldredge Я попробую посмотреть сборку, хотя я не очень хорошо разбираюсь в ассемблерном коде ARM.

David Yue 24.06.2024 00:57

@Бармар Я не уверен, насколько меньше тебе нужен этот пример. Я мог бы предоставить простую программу на C, которая также выдает ошибки, но тогда это не помогло бы моей проблеме, поскольку я уже знал бы, что не так. Все файлы, которые я связал по ссылке на github, необходимы для компиляции моего кода. Пожалуйста, дайте мне знать, если у вас есть предложения о том, как я мог бы улучшить этот вопрос.

David Yue 24.06.2024 01:00

Идея состоит в том, что вы, насколько это возможно, создаете небольшую автономную программу, которая вызывает только эту функцию, а все остальное удаляется или заглушается.

Nate Eldredge 24.06.2024 01:02

Ах, я вижу это. dominators.h объявляет ComputeDominanceFrontier возвращающим void, и это объявление видит вызывающий объект, но на самом деле он возвращает HashTable. Я напишу ответ, объясняющий, как это вызывает то, что вы видите.

Nate Eldredge 24.06.2024 01:11

Или я думаю нет, так как он был закрыт. Но: когда функция возвращает достаточно большое struct, вызывающая сторона должна выделить место для возвращаемого значения и передать на него указатель (в x8 для aarch64). Поскольку компилятор видит, что dominance_frontier будет использоваться в качестве возвращаемого значения, он оптимизирует пространство своего стека (даже в -O0) и вместо этого использует переданный указатель из x8 в качестве своего адреса. Здесь, поскольку main думал, что функция вернулась void, регистр x8 вместо этого просто содержит мусор.

Nate Eldredge 24.06.2024 01:17

Если бы вы включили dominators.h в dominators.c, как обычно, компилятор бы это уловил.

Nate Eldredge 24.06.2024 01:18

@NateEldredge ОГРОМНОЕ СПАСИБО! В этом была проблема! Не могу поверить, что я этого не видел. Я проголосовал за возобновление вопроса. Я приму ваш ответ.

David Yue 24.06.2024 01:19
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
15
166
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

dominators.c

HashTable ComputeDominanceFrontier (struct Function *function)
^^^^^^^^^

dominators.h

void ComputeDominanceFrontier (struct Function *function);
^^^^

Упс.

Функция ComputeDominanceFrontier вызывается из main.c, которая включает dominators.h, поэтому вызывающая сторона думает, что она возвращает void. Но согласно определению в dominators.c, на самом деле оно возвращает HashTable. Это несоответствие является причиной сбоя, как описано ниже.

Компилятор этого не улавливает, потому что вы не включили dominators.h в dominators.c, поэтому при компиляции dominators.c он понятия не имел, что какой-то другой исходный файл видел конфликтующее объявление. По этой причине, когда заголовок объявляет глобальную функцию, вы всегда должны убедиться, что заголовок включен в исходный файл, определяющий функцию. Вы можете помочь обеспечить это с помощью -Wmissing-prototypes, который выдает предупреждение всякий раз, когда глобальная функция определена без предварительного объявления. См. Предупреждение компилятора для функции, определенной без прототипа в области видимости?


Происходит следующее: на Arm64, как и на многих других платформах, когда функция возвращает тип struct, который не может быть возвращен в регистре, он возвращается по «скрытой ссылке»: вызывающая сторона выделяет место для возвращаемого значения и передает дополнительный скрытый аргумент с адресом этого пространства. В Arm64 дополнительный аргумент передается в регистр x8. Затем вызываемая функция отвечает за копирование возвращаемого значения в это пространство.

В этом случае, поскольку main не знал, что ComputeDominanceFrontier возвращает тип struct, он не выделил такое пространство и оставил x8, содержащий мусор (в вашем случае его значение оказалось 1).

При наивной компиляции вы ожидаете увидеть сбой в конце функции, когда dominance_frontier копируется из стека в фиктивный адрес возвращаемого значения. И похоже, что именно это вы видели в своем тесте Ubuntu (в котором, позвольте угадаю, вместо clang использовался gcc?).

Однако компилятор может оптимизировать это: вместо использования пространства стека для dominance_frontier используйте пространство возвращаемого значения, выделенное вызывающей стороной. Тогда нам не нужно копировать эти данные перед возвратом, поскольку они были заполнены «на месте». Похоже, что clang выполняет эту оптимизацию, даже когда оптимизация «выключена». Таким образом, dominance_frontier на самом деле не живет в фрейме стека ComputeDominanceFrontier, и &dominance_frontier является не адресом стека, а скорее адресом возвращаемого значения, переданным вызывающей стороной, что в данном случае является мусором.

Еще раз спасибо! Я добавлю -Wmissing-prototypes в свой make-файл.

David Yue 24.06.2024 01:57

@thebusybee: У ОП уже было -Wall -Wextra. Они не включают -Wmissing-prototypes, потому что нет ничего плохого в том, чтобы не объявлять функцию до ее определения, а в небольшой программе (например, файле с одним исходным кодом) это было бы бессмысленно и раздражающе.

Nate Eldredge 24.06.2024 08:18

Из публикуемого вами кода (никакого кода, кроме снимков экрана отладчика) можно сделать вывод, что вы используете:

I'm running into a bug in my code where the address of a local stack variable is invalid. I've been trying to debug this with lldb.

In the lldb prompt below, you can see that &dominance_frontier gives the address 0x0000000000000001 which then causes a SIGSEGV on hash_table_init call line 121. However &dominator_tree_adj, gives a valid address. I'm completely baffled as to why this might be the case.

Process 70998 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = step over
    frame #0: 0x0000000100006fd0 ir`ComputeDominanceFrontier(function=0x00006000030f8000) at dominators.c:121:9
   111          struct Array postorder_traversal = postorder (function->entry_basic_block);
   112          struct DFAConfiguration config = DominatorDFAConfiguration (function);
   113          struct DFAResult result = run_DFA (&config, function);
   114  
   115          HashTable dominator_tree_adj = ComputeDominatorTree (function, &result);
   116          printf("%p\n", &dominator_tree_adj);
   117          HashTable dominance_frontier;
   118          printf("%ld\n", sizeof(dominance_frontier));
   119  
   120  
-> 121          hash_table_init (&dominance_frontier);
   122          // Compute the transpose graph from the dominator tree adjacency list
   123          // Each node is guaranteed to have only one direct predecessor, since
   124          // each node can only have one immediate dominator. We will need this
   125          // in the DF algorithm below
   126          HashTable dominator_tree_transpose;
   127          hash_table_init (&dominator_tree_transpose);
   128  
   129          struct HashTableEntry *entry;
   130      

в строке 117 у вас есть

HashTable dominance_frontier;

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

Вероятно, ваш код должен был быть

    HashTable *dominator_tree_transpose = hash_table_create(number_of_hash_entries);
    hash_table_init(dominator_tree_transpose);

но поскольку вы не предоставили исходный код, я не могу дальше гадать. Я не верю, что вы можете вызвать hash_table_init(), передав ему адрес ОБЪЕКТА. Но я могу ошибаться. В следующий раз опубликуйте исходный код... не сеанс отладки. Включите полное определение типа HashTable, функций hash_table_create() и hash_table_init(), поскольку использование обеих функций не может быть выведено из опубликованного кода.

Примечание. Я публикую это как ответ, потому что это, по-видимому, является причиной неудачи. Но из приведенных фрагментов это недостаточно ясно.

В идеале это было бы в рамках вопроса, но ОП связал полный код, и hash_table_init все в порядке; он выполняет инициализацию. github.com/CoconutJJ/compiler-optimization/blob/…

Nate Eldredge 28.06.2024 15:54

Ну, я видел это позже, но этот пример не соответствует требованиям SO относительно минимальности и воспроизводимости. Необходимость создания полноценного приложения — это не способ показать вещи. Вместо того, чтобы голосовать за закрытие вопроса, я предпочел ответить исходя из написанного, а не копаться в целом многопроектном git-репозитории.

Luis Colorado 04.07.2024 13:08

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