NASM: _malloc выделяет слишком много памяти

Я пытаюсь использовать динамическое размещение в сборке под MacosX и столкнулся с довольно странным поведением при вызове _malloc из библиотеки C.

Моей конечной целью было бы выделить в памяти что-то вроде 10 байт, но при записи данных за пределы выделенного фрагмента программа не дает сбоя.

Возможно, я не понимаю, как работает _malloc.

Это тестовый код, который я написал. Сначала я попытался увеличить размер стека, как это, кажется, делает Кристофер Свенсон . Потом я прочитала этот пост, в котором вроде бы написано, что размер должен быть rdi. Поскольку последнее появилось позднее, я предположил, что оно также и самое правильное.

extern _malloc ;; malloc from the C library
section .text
    global _start

_start:
  xor rdi, rdi
  mov rdi, 10         ;; I try to allocate 10 bytes
  call _malloc        ;; The address should be in rax
  mov qword [rax], 1  ;; writing some data where it is supposed to work
  add rax, 11         ;; going outside the allocated chunk
  mov qword [rax], 1  ;; writing some data where it is not supposed to work

  mov rax, 0x2000001
  mov rdi, 0
  syscall

Для простоты я не проверяю rax, предполагая, что это не NULL.

Я скомпилировал с:

$ nasm -f macho64 -o test.o test.asm
$ gcc -e _start test.o -lc -m64 -o a.out -Wl,-no_pie

Может ли кто-нибудь объяснить, почему я неправильно использую _malloc или почему запись за пределами выделенного фрагмента не приводит к сегфолту?

Вам вообще не гарантирован сегфолт. Вы даже не уверены, что это вызовет проблемы где-то в будущем.

Mat 08.06.2024 12:55
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
0
1
57
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Когда вы выделяете память с помощью malloc и выделение проходит успешно, выделенная вами память гарантированно будет доступна. Однако нет никакой гарантии, что другая память будет недоступна. Доступ к нему не гарантирован. Так что не полагайтесь на это.

На практике malloc запрашивает у операционной системы большие фрагменты памяти за раз, а затем распределяет эти фрагменты по различным небольшим выделениям, которые вы делаете. Поэтому очень часто можно увидеть, что доступно больше памяти, чем вы выделили. Однако эту память нельзя использовать, она не принадлежит вашему выделению. Вместо этого malloc, скорее всего, отдаст эту память для будущих выделений. Его также можно использовать для других целей, например, для ведения бухгалтерского учета. Так что не обращайтесь к нему.

MMU не может защитить память на уровне байтов. Он защищает страницы, размер которых зависит от оборудования и ОС. 4 Кбайт, вероятно, типично.

Кроме того, хотя это зависит от ОС, и я не знаю подробностей о MacOS, обычно защита MMU применяется на уровне процесса, а не на уровне отдельного распределения. Таким образом, вы получаете ошибку страницы только при доступе к памяти вне вашего процесса.

Процесс запрашивает память у ОС, которая будет предоставляться частями размером со страницу или кратными им, и эта память добавляется в кучу процесса, после чего вся куча принадлежит процессу, и если процесс превышает отдельное выделение, но остается в пределах памяти процесса, он не будет обнаружен как страничная ошибка, поскольку она таковой не является.

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

Чтобы достичь того, что вы ожидаете, MMU потребуется размер страницы в 1 байт, что непрактично. Таблица страниц MMU может оказаться больше, чем объем памяти, которую она пытается защитить!

обычно защита MMU применяется на уровне процесса, а не на уровне отдельного распределения - ИДК, какую гипотетическую альтернативу вы вообще рассматриваете. Таким образом, процессор будет отслеживать, как был получен указатель, поэтому [rax + rcx] может вызвать ошибку, если находится за пределами выделения, содержащего rax, даже если результирующий адрес находился внутри другой допустимой страницы? (Вроде того, как правила C делают UB доступным за пределами объекта, из которого был получен указатель.) В любом случае, как вы говорите, распределение работает путем сопоставления страниц в виртуальном адресном пространстве вашего процесса, поэтому они поддерживаются оперативной памятью, а не срабатывают. .

Peter Cordes 08.06.2024 19:45

«IDK, какую гипотетическую альтернативу вы вообще рассматриваете», например, «гипотетически» стеки потоков внутри процесса могут быть защищены отдельно. Я понятия не имею, сделано ли это на MacOS. Я не хотел проявлять слишком сочувствие, поскольку меня это не знает (или меня это не волнует).

Clifford 08.06.2024 21:01

О, если у каждого потока есть свои таблицы страниц? Да, это было бы гипотетически возможно (с большей работой для ОС и большей недействительностью TLB при переключении между потоками одного и того же процесса), но это сломало бы C, потому что в C законно хранить указатель на локальную переменную в чем-то глобально доступном, что позволяет другим потокам получать доступ к вашей памяти стека (если только реализация не использовала стек asm для автоматического хранения). Это также не имеет значения для malloc, который не может выделить память в стеке, поскольку выделение должно продолжаться после возврата функции (или даже выхода из этого потока).

Peter Cordes 09.06.2024 01:48

@PeterCordes Я предлагал отделить стек от кучи и статический/глобальный, чтобы у процесса была куча и статический/глобальный, а у каждого потока был свой собственный стек. Таким образом, таблица страниц имеет фиксированную кучу и глобальные регионы и динамически переключает регион стека. Я работаю над встроенными системами с ядрами RTOS, где все представляет собой поток, без модели процесса, и те, которые поддерживают MMU, безусловно, делают то же самое или что-то подобное.

Clifford 09.06.2024 09:20

Да, я думал, что вы это имеете в виду, хотя я не знал, что системы RTOS действительно работают таким образом. Это сделало бы невозможным делать вещи, которые разрешены в C и C++, например иметь локальный std::atomic<int> stack_var = 1; в стеке и делать shared_var = &stack_var;, чтобы другие потоки могли читать и/или записывать память вашего стека. Поскольку основные операционные системы, такие как macOS и Linux, поддерживают модель потоков ISO C, им необходимо, чтобы все потоки процесса использовали одну и ту же карту памяти (таблицы страниц).

Peter Cordes 09.06.2024 19:04

@PeterCordes обычно защита стека не является обязательной для каждого потока, а общая память обычно выделяется статически. В C есть много легального, но не подходящего для встроенных систем, критичных к безопасности или высокой целостности. Концептуально приложение и RTOS — это одна и та же программа, а ядро ​​— это просто статическая библиотека. Таким образом, доступность находится в руках разработчика на уровне приложения.

Clifford 09.06.2024 20:57

Конечно. Вы задавались вопросом, делают ли основные настольные операционные системы, такие как macOS, то же самое. И одна из причин, почему бы и нет, заключается в том, что это станет препятствием для моделей памяти с поддержкой потоков в основных языках. (Или, что более реалистично, эти модели потоков разработаны таким образом, потому что операционные системы уже работали таким образом, когда все потоки одного и того же процесса используют одни и те же таблицы страниц.)

Peter Cordes 09.06.2024 22:04

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