Я не думаю, что для этого есть какой-либо стандартный POSIX API.
Синтаксический анализ /proc/self/maps
- ваш лучший выбор.(Там может быть библиотека, чтобы помочь с этим, но IDK).
Вы пометили этот ASLR, хотя.Если вы просто хотите узнать, где находятся сегменты text / data / bss, вы можете поместить метки в их начало / конец, чтобы эти адреса были доступны в C. Например, extern const char bss_end[];
будет хорошим способом ссылаться на метку, которую вы поместилив конце BSS, используя скрипт компоновщика и, возможно, какой-то рукописный асм.Генерируемый компилятором asm будет использовать REA-относительную инструкцию LEA, чтобы получить адрес в регистре относительно текущего адреса инструкции (который процессор знает, потому что он выполняет код, отображенный там).
Или, может быть, простоскрипт компоновщика и объявление фиктивных переменных C в пользовательских разделах.
Я не уверен, что вы можете сделать это для отображения стека.В большой среде и / или argv начальный стек при входе в main()
или даже _start
может не совпадать с той же страницей, что и самый высокий адрес в отображении стека.
Для сканирования вам нужно либо перехватить SIGSEGV
, либо сканировать с помощью системных вызовов вместо загрузки или сохранения в пространстве пользователя.
mmap
и mprotect
не могут запросить старую настройку,поэтому они не очень полезны для неразрушающих вещей.mmap
с подсказкой, но без MAP_FIXED
может отобразить страницу, а затем вы можете munmap
ее.Если фактический выбранный адрес! = Подсказка, то можно предположить, что адрес использовался.
Возможно, лучшим вариантом будет сканирование с помощью madvise(MADV_NORMAL)
и проверка на EFAULT
, но только одна страница навремя.
Вы даже можете сделать это с помощью errno=0; posix_madvise(page, 4096, POSIX_MADV_NORMAL)
.Затем проверьте errno
: ENOMEM
: Адреса в указанном диапазоне частично или полностью находятся вне адресного пространства вызывающего.
В Linux с madvise(2)
вы можете использовать MADV_DOFORK
иличто-то, что даже менее вероятно, будет с настройкой не по умолчанию для каждой страницы.
Но в Linux, еще лучший выбор для запросов только для чтения к отображению памяти процесса - mincore(2)
: он также использует код ошибки ENOMEM
для недопустимых адресов в запрашиваемом диапазоне.Msgstr "addr
to addr + length
содержит не отображённую память ".(EFAULT
для вектора результата, указывающего на не отображенную память, а не на адрес).
Полезен только результат errno
;результат vec
показывает, загружены ли страницы в оперативной памяти или нет.(Я не уверен, показывает ли он, какие страницы подключены к таблицам страниц HW, или он будет считать страницу, которая находится в памяти в кэше страниц для файла с отображением в памяти, но не является проводной, поэтому при доступе будет запускаться программныйошибка страницы).
Вы можете выполнить двоичный поиск конца большого отображения, вызвав mincore
с большей длиной.
Но, к сожалению, я не вижулюбой эквивалент для нахождения следующего сопоставления после не отображенной страницы, что было бы гораздо более полезным, поскольку большая часть адресного пространства будет не отображена.Особенно в x86-64 с 64-битными адресами!
Для разреженных файлов есть lseek(SEEK_DATA)
.Интересно, работает ли это на Linux /proc/self/mem
?вероятно, нет.
Так что, возможно, большие (например, 256 МБ) вызовы (tmp=mmap(page, blah blah)) == page
были бы хорошим способом для сканирования не отображенных областей в поисках отображенных страниц. В любом случае вы просто munmap(tmp)
, будь тоmmap
использовал ваш адрес подсказки или нет.
Синтаксический анализ /proc/self/maps
почти наверняка более эффективен.
Но наиболее эффективная вещь будет помещать меткигде вы хотите их для статических адресов, и отслеживания динамического распределения, чтобы вы уже знали, где находится ваша память.Это работает, если у вас нет утечек памяти.(glibc malloc
может иметь API для обхода сопоставлений, но я не уверен.)
Обратите внимание, что любой системный вызов вызовет errno=EFAULT
если вы передадите ему не назначенный адрес для параметра, который должен указывать на что-то.
Один из возможных кандидатов - access(2)
, который принимает имя файла и возвращает целое число.Он никак не влияет на состояние чего-либо еще, успех или неудача, но недостатком является доступ к файловой системе, если указанная память является допустимой строкой пути.И он ищет строку C неявной длины, поэтому он также может быть медленным, если передать указатель на память без байта 0
где-либо в ближайшее время.Я предполагаю, что ENAMETOOLONG
включится, но он все равно определенно прочитает каждую доступную страницу, на которой вы его используете, с ошибкой, даже если она была выгружена.
Если вы откроете файловый дескриптор на /dev/null
, вымог сделать write()
системные вызовы с этим. Или даже с writev(2)
: writev(devnull_fd, io_vec, count)
, чтобы передать ядру вектор указателей в одном системном вызове и получить EFAULT, если какой-либо из них плохой.(С длиной 1 байт каждый). Но (если драйвер /dev/null
не пропускает чтение достаточно рано), он действительно считывает допустимые страницы, в отличие от mincore()
.В зависимости от того, как он реализован внутри, драйвер /dev/null
может увидеть запрос достаточно рано для его реализации «возврат истины» -без-делания-чего-либо, чтобы избежать фактического прикосновения к страницам после проверки на EFAULT.Было бы интересно проверить.