Usr lib libc so

Anyone knows how this kind of stuff is generated?

2 Answers 2

This is generated when glibc is compiled using Make utility.

There is a rule (started by make install ) in glibc’s Makefile, which does just echo needed lines into some temporary file $@.new :

And then this file is renamed to libc.so

Here is a comment from Makefile, which explains a bit:

I understand this as: libc.so.6 is not complete and needs something, which can’t be stored in shared library. So, glibc developers moved this something to static part of glibc — libc_nonshared.a . To force always linking both libc.so.6 and libc_nonstared.a , they created a special linking script which instructs ld linker to use both when it is asked for -lc (libc)

What is in the nonshared part? Let’s check:

There are atexit() , *stat*() , mknod functions. Why? Don’t know really, but it is a fact of glibc.

Каждый знает, как генерируется этот вид вещи?

Это генерируется, когда Glibc скомпилирован с помощью служебной программы.

Существует правило (запускается make install ) в Makefile GLibC, который делает только эхо необходимых строк в какой — то временный файл $@.new :

А затем этот файл переименовывается в libc.so

Вот комментарий от Makefile, который объясняет немного:

Я понимаю это так: libc.so.6 не является полным и нужно что — то, что не может быть сохранена в общей библиотеке. Так, Glibc разработчики перенесли это что — то статическую часть Glibc — libc_nonshared.a . Для того, чтобы заставить всегда связывая как libc.so.6 и libc_nonstared.a они создали специальный скрипт , связывающий который инструктирует Л.Д. линкер использовать оба , когда он просил -lc (Libc)

Что в NonShared части? Давай проверим:

Читайте также:  The last of us эмулятор на пк

Есть atexit() , *stat*() , mknod функция. Зачем? Не знаю , на самом деле, но это факт Glibc.

В качестве упражнения я хочу написать программу на С. Достаточно простую для того, чтобы дизассемблировать ее и объяснить весь код самой себе.

Звучит несложно, правильно?

У читателя предполагается наличие опыта компиляции программ и работы в Линуксе. Небольшое умение читать ассемблерный код тоже пригодится.

Итак, вот наш простейший хелловорлд:

Скомпилируем его и посчитаем количество символов:

Фигасе! Откуда берутся эти 11 килобайт? objdump -t hello показывает 79 записей в таблице идентификаторов, за большинство из которых ответственна стандартная библиотека.

Так что мы не будем ее использовать. И printf мы тоже не будем использовать, чтобы избавиться от инклюда:

Перекомпилируем и пересчитаем количество символов:

Почти ничего не изменилось? Ха!

Проблема в том, что gcc все ещё использует startup files (?) во время линкования. Доказательства? Скомпилируем с ключом -nostdlib , после чего (в соответствии с документацией) gcc «не будет использовать при линковании системные библиотеки и startup files. Использоваться будут только явно переданные линкеру файлы».

Всего лишь предупреждение, все равно попробуем:

Выглядит неплохо! Мы уменьшили размер до значительно более вменяемого (аж на целый порядок!)…

…и заплатили за это сегфолтом. Блин.

Для развлечения сделаем нашу программу запускаемой до того, как начнем разбираться в ассемблере.

Что же делает символ _start , который похоже нужен для запуска программы? Где он обычно определяется при использовании libc?

По умолчанию с точки зрения линкера именно _start , а не main , является настоящей точкой входа в программу. Обычно _start определяется в перемещаемом ELF crt1.o . Убедимся в этом, слинковав хелловорлд c crt1.o и заметив, что _start теперь обнаруживается (но взамен появились другие проблемы из-за того, что не определены другие startup symbols libc):

Читайте также:  Pflth rf d c

Проверка сообщила, что на этом компьютере _start живет в исходнике libc: sysdeps/x86_64/elf/start.S . Этот восхитительно комментированный файл экспортирует символ _start , инициализирует стек, некоторые регистры и вызывает __libc_start_main . Если посмотреть в самый низ csu/libc-start.c , можно увидеть вызов _main нашей программы:

Так вот зачем нужен _start . Для удобства подытожим происходящее между _start и вызовом main : инициализировать кучу вещей для libc и вызвать main . А раз libc нам не нужен, экспортируем собственный символ _start , который только и умеет, что вызывать main , и слинкуем с ним:

Скомпилируем и выполним хелловорлд с ассемблерной заглушкой _start :

Ура, с компиляцией проблем больше нет. Но сегфолт никуда не делся. Почему? Скомпилируем с отладочной информацией и заглянем в gdb. Установим брейкпоинт на main и пошагово исполним программу до сегфолта:

Что? main исполняется два раза? …Пришло время взяться за ассемблер:

Хех! Подробный разбор ассемблера оставим на потом, отметив вкратце следующее: после возврата из callq в main мы исполняем несколько nop и возвращаемся прямо в main . Поскольку повторный вход в main был осуществлен без установки указателя инструкции возврата на стеке (как части стандартной подготовки к вызову функции), второй вызов retq пытается достать из стека фиктивный указатель инструкции возврата и программа вылетает. Нужен способ завершения.

Буквально. После возврата из callq в %eax делается push 1 , код системного вызова sys_exit , и т.к. нужно сообщить о правильном завершении кладем в %ebx 0 , единственный аргумент SYS_exit . Теперь входим в ядро с прерыванием int $0x80 .

Ура! Программа компилируется, запускается, при прогоне через gdb даже нормально завершается.

Привет из свободного от libc мира!

Оставайтесь со мной, во второй части разберем ассемблерный код подробно, посмотрим что случится, если сделать программу более сложной, и еще немного разберемся в линковании, соглашениях о вызовах и структуре двоичного ELF файл в х86 архитектуре.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *