ELF это аббревиатура от английского Executable and Linking Format (Формат Исполняемых и Связываемых файлов). Это одна из разновидностей форматов для исполняемых и объектных файлов, используемых в UNIX-системах. Для нас особый интерес будет представлять заголовок файла. Каждый файл формата ELF имеет ELF-заголовок следующей структуры:
typedef struct < unsigned char e_ident[EI_NIDENT]; /* Сигнатура и прочая информация */ Elf32_Half e_type; /* Тип объектного файла */ Elf32_Half e_machine; /* Аппаратная платформа (архитектура) */ Elf32_Word e_version; /* Номер версии */ Elf32_Addr e_entry; /* Адрес точки входа (стартовый адрес программы) */ Elf32_Off e_phoff; /* Смещение от начала файла таблицы программных заголовков */ Elf32_Off e_shoff; /* Смещение от начала файла таблицы заголовков секций */ Elf32_Word e_flags; /* Специфичные флаги процессора */ /* (не используется в архитектуре i386) */ Elf32_Half e_ehsize; /* Размер ELF-заголовка в байта х */ Elf32_Half e_phentsize; /* Размер записи в таблице программных заголовков */ Elf32_Half e_phnum; /* Количество записей в таблице */ /* программных заголовков */ Elf32_Half e_shentsize; /* Размер записи в таблице заголовков секций */ Elf32_Half e_shnum; /* Количество записей в таблице */ /* заголовков секций */ Elf32_Half e_shstrndx; /* Расположение сегмента, содержащего таблицy стpок */ >Elf32_Ehdr;
В этой структуре, поле «e_entry» содержит адрес запуска программы.
Видео #32. Запуск Windows-программ на Linux
Что находится по адресу «0x080482d0», то есть по адресу запуска (starting
Для ответа на этот вопрос попробуем дизассемблировать программу «simple». Для дизассемблирования исполняемых файлов я использую objdump.
objdump disassemble simple
Утилита objdump выдаст очень много информации, поэтому я не буду приводить её всю. Нас интересует только адрес 0x080482d0. Вот эта часть листинга:
080482d0 : 80482d0: 31 ed xor %ebp,%ebp 80482d2: 5e pop %esi 80482d3: 89 e1 mov %esp,%ecx 80482d5: 83 e4 f0 and $0xfffffff0,%esp 80482d8: 50 push %eax 80482d9: 54 push %esp 80482da: 52 push %edx 80482db: 68 20 84 04 08 push $0x8048420 80482e0: 68 74 82 04 08 push $0x8048274 80482e5: 51 push %ecx 80482e6: 56 push %esi 80482e7: 68 d0 83 04 08 push $0x80483d0 80482ec: e8 cb ff ff ff call 80482bc 80482f1: f4 hlt 80482f2: 89 f6 mov %esi,%esi
Похоже на то, что первой запускается процедура «_start». Все, что она делает это очищает регистр ebp, «проталкивает» какие-то значения в стек и вызывает подпрограмму. Согласно этим инструкциям содержимое стека должно выглядеть так:
——Дно стека—— 0x80483d ——————- esi ——————- ecx ——————- 0x8048274 ——————- 0x8048420 ——————- edx ——————- esp ——————- eax ——————-
Теперь вопросов становится еще больше
- Что за числа кладутся в стек?
- Что находится по адресу 80482bc, который вызывается инструкцией call в процедуре _start?
- В приведенном листинге отсутствуют инструкции, инициализирующие регистры (имеются ввиду eax, ecx, edx прим. перев.). Где они инициализируются?
Попробуем ответить на все эти вопросы.
Запуск команд Linux в фоновом режиме. Командная строка Linux
Вопрос 1> Что за числа кладутся в стек?
Если внимательно просмотреть весь листинг, создаваемый утилитой objdump, то можно легко найти ответ
0x80483d0 : Это адрес функции main().
0x8048274 : адрес функции _init.
0x8048420 : адрес функции _fini. Функции _init и _fini это функции инициализации и финализации (завершения) приложения, генерируемые компилятором GCC.
Таким образом все приведенные числа являются указателями на функции (точнее адресами функций прим. перев.)
Вопрос 2> Что находится по адресу 80482bc?
Снова обратимся к листингу.
80482bc: ff 25 48 95 04 08 jmp *0x8049548
Здесь *0x8049548 означает указатель.
Это просто косвенный переход по адресу, хранящемуся в памяти по адресу 0x8049548.
Дополнительно о формате ELF и динамическом связывании
Формат ELF предполагает возможность динамического связывания исполняемой программы с библиотеками. Где под словами «динамическое связывание» следует понимать то, что связывание производится во время исполнения. В противоположность динамическому связыванию существует «статическое связывание», т.е. когда связывание с библиотеками происходит на этапе сборки программы, что, как правило, приводит к «раздуванию» исполняемого файла до огромных размеров. Если вы запустите команду:
«ldd simple» libc.so.6 => /lib/i686/libc.so.6 (0x42000000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
Вы сможете увидеть полный список библиотек, связанных с программой simple динамически. Вкратце, концепция динамического связывания выглядит так.
- На этапе сборки программы адреса переменных и функций в динамической библиотеке не известны. Они становятся известны только на этапе исполнения
- Для того, чтобы иметь возможность обращаться к компонентам динамической библиотеки (переменные, функции и т.д. прим. перев.) необходимо предусмотреть указатели на эти компоненты Указатели заполняются фактическими адресами во время загрузки.
- Приложение может обращаться к динамическим компонентам только косвенно, используя для этого указатели. Пример такой косвенной адресации можно увидеть в листинге, приведенном выше, по адресу 80482bc, когда осуществляется косвенный переход. Фактический адрес перехода сохраняется по адресу 0x8049548 во время загрузки программы.
Косвенные ссылки можно посмотреть, выполнив команду
objdump -R simple simple: file format elf32-i386 DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE 0804954c R_386_GLOB_DAT __gmon_start__ 08049540 R_386_JUMP_SLOT __register_frame_info 08049544 R_386_JUMP_SLOT __deregister_frame_info 08049548 R_386_JUMP_SLOT __libc_start_main Здесь адрес 0x8049548 называется «jump slot» и имеет определенный смысл. В соответствии с таблицей он означает вызов __libc_start_main.
Что такое __libc_start_main?
Теперь «карты сдает» библиотека libc. __libc_start_main это функция из библиотеки libc.so.6. Если отыскать функцию __libc_start_main в исходном коде библиотеки glibc, то увидите примерно такое объявление.
extern int BP_SYM (__libc_start_main) (int (*main) (int, char **, char **), int argc, char *__unbounded *__unbounded ubp_av, void (*init) (void), void (*fini) (void), void (*rtld_fini) (void), void *__unbounded stack_end) __attribute__ ((noreturn));
Теперь становится понятен смысл ассемблерных инструкций из листинга, приведенного выше они кладут на стек входные параметры и вызывают функцию __libc_start_main.
В задачу этой функции входят некоторые действия по инициализации среды исполнения и вызов функции main().
Рассмотрим содержимое стека с новых позиций.
Дно стека —————— 0x80483d0 main —————— esi argc —————— ecx argv —————— 0x8048274 _init —————— 0x8048420 _fini —————— edx _rtlf_fini —————— esp stack_end —————— eax это ноль (0) ——————
Согласно такому представлению стека, понятно, что перед вызовом __libc_start_main() в регистры esi, ecx, edx, esp и eax должны быть записаны соответствующие значения.
Совершенно очевидно, что дизассемблированный код, показанный выше, ничего в эти регистры не пишет. Тогда кто? Остается только одно предположение ядро. А теперь перейдем к третьему вопросу.
Вопрос 3> Что делает ядро?
Когда программа запускается из командной строки, выполняются следующие действия.
- Командная оболочка (shell) делает системный вызов «execve» с параметрами argc/argv.
- Обработчик системного вызова в ядре получает управление и начинает его обработку. В ядре обработчик называется «sys_execve». На платформе x86, пользовательское приложение передает аргументы вызова в ядро через регистры.
- ebx : указатель на строку с именем программы
- ecx : указатель на массив argv
- edx : указатель на массив переменных окружения
- Универсальный обработчик системного вызова в ядре называется do_execve. Он создает и заполняет определенные структуры данных, копирует необходимую информацию из пространства пользователя в пространство ядра и, наконец, вызывает search_binary_handler().
Linux поддерживает множество форматов исполняемых файлов, например a.out и ELF. Для обеспечения такой поддержки в ядре имеется структура «struct linux_binfmt», которая содержит указатели на загрузчики каждого из поддерживаемых форматов. Таким образом, search_binary_handler() просто отыскивает нужный загрузчик и вызывает его. В нашем случае это load_elf_binary(). Описывать эту функцию в подробностях слишком долгая и нудная работа, так что я не буду заниматься этим здесь. За подробностями обращайтесь к специальной литературе по данной тематике. (от себя могу предложить ссылку на статью «Внутреннее устройство ядра Linux 2.4» прим. перев. )
Вкратце процесс загрузки выглядит примерно так.
Сначала создаются и заполняются структуры в пространстве ядра и файл программы считывается в память. Затем производится установка дополнительных значений определяется размер сегмента кода, определяется начало сегмента данных и сегмента стека и т.д.. В пользовательском режиме выделяется память, в которую копируются входные параметры (argv) и переменные окружения. Затем функция create_elf_tables(), в пользовательском режиме, кладет на стек argc, указатели на argv и массив переменных окружения, после чего start_thread() запускает программу на исполнение.
Когда управление передается в точку _start, стек выглядит примерно так:
Дно стека ————- argc ————- указатель на argv ————- указатель на env ————-
Теперь наш дизассемблированный листинг выглядит еще более определенным.
pop %esi
Теперь все готово к запуску программы.
Что можно сказать по-поводу остальных регистров?
esp используется для указания вершины стека в прикладной программе. После того как со стека будет снята вся необходимая информация, процедура _start просто скорректирует указатель стека (esp), сбросив 4 младших бита в регистре esp. В регистр edx заносится указатель на, своего рода деструктор приложения rtlf_fini. На платформе x86 эта особенность не поддерживается, поэтому ядро заносит туда число 0 макрокомандой.
#define ELF_PLAT_INIT(_r) do < _r->ebx = 0; _r->ecx = 0; _r->edx = 0; _r->esi = 0; _r->edi = 0; _r->ebp = 0; _r->eax = 0; > while (0)
Откуда взялся весь этот дополнительный код
Откуда взялся весь этот дополнительный код? Он входит в состав компилятора GCC. Вы можете найти его в /usr/lib/gcc-lib/i386-redhat-linux/XXX и /usr/lib где XXX номер версии gcc. Файлы называются crtbegin.o,crtend.o, gcrt1.o.
Подведение итогов
Итак, выводы следующие.
- При сборке программы, GCC присоединяет к ней код из объектных модулей crtbegin.o/crtend.o/gcrt1.o а другие библиотеки, по-умолчанию, связывает динамически. Адрес запуска приложения (в ELF-заголовке прим. перев.) указывает на точку _start.
- Ядро загружает программу и устанавливает сегменты text/data/bss/stack, распределяет память для входных параметров и переменных окружения и помещает на стек всю необходимую информацию.
- Управление передается в точку _start. Здесь информация снимается со стека, на стеке размещаются входные параметры для функции __libc_start_main, после чего ей передается управление.
- Функция __libc_start_main выполняет все необходимые действия по инициализации среды исполнения, особенно это касается библиотеки C (malloc и т.п.) и вызывает функцию main() программы.
- Функции main() передаются входные аргументы main(argc, argv). Здесь есть один интересный момент. __libc_start_main «представляет» себе сигнатуру функции main() как main(int, char **, char **). Если вам это любопытно, то попробуйте запустить следующую программу:
main(int argc, char** argv, char** env) < int i = 0; while(env[i] != 0) < printf(«%sn», env[i++]); >return(0); >
Заключение
В Linux запуск функции main() является результатом взаимодействия GCC, libc и загрузчика.
Источник: codenet.ru
Как сделать исполняемый файл? [Решено]
Как в линуксе сделать исполняемый файл командной строки. Что то немогу догнать.
Например: есть «паровозик» консольных команд, и не хочется это набивать вручную, помнится в виндусе надо было создать файл с расширением .bat-помоему. А в линуксе как это делается?
Mike — 15 Апрель, 2009 — 11:10
sudo chmod +x
На форуме было и не раз!
picaro — 15 Апрель, 2009 — 11:36
Или открыть в файл манагере (например, konqueror) и мышей поставить галочку «Сделать файл исполняемым» (как-то так называется).
balamutick — 15 Апрель, 2009 — 11:57
ПКМ щелкаешь по файлу, выбираешь свойства и там настраиваешь (верно для КДЕ3, в 4-ых вероятно так же).
***
Из КДЕ файл так и будет запускаться приложением, которое за него отвечает, напишите в консоли ./имя_приложения и нажмите выполнить его(энтер).
(или выберите в mc курсором и нажмите «ввод»(энтер))
У меня например, файл из примера (linux-carmanniy_spravochnik.djvu), после превращения в выполняемый в попрежнему запускался Djveiw, а вот при запуске из консоли выдало честно:
./linux-carmanniy_spravochnik.djvu
bash: ./linux-carmanniy_spravochnik.djvu: не могу запустить бинарный файл
Что показывает, что все честно работает, т.е. пытается запуститься, просто файл не бинарный.
«Паровозик» же (скрипт, командный файл и т.п. названия), как пример напишу ниже:
#!/bin/bash
#останавливаем программу MLdonkey
killall mlnet
#создаем в дом. каталоге папку
mkdir /home/user/backup
#копируем резервную копию настроек MLdonkey.
cp -r /home/user/.mldonkey/ /home/user/backup/
#запускаем программу MLdonkey снова
mlnet
Его вы должны сохранить в текстовый файл, разрешить на выполнение этот файл и запустить. Замечу также, что команды и программу сделал ссылками, чтобы вы могли легко посмотреть описание команд в ВикиПедии.
Значок # (решетка, означает коментарий, эта часть не обязательна, и может быть опущена, исключение строка #!/bin/bash — она указывает системе, что это исполняемый скрипт написанный командами для bash)
Гость — 23 Февраль, 2012 — 19:32
Источник: kubuntu.ru
Как запустить сценарий оболочки в Linux [Основы для начинающих]
Есть два способа запустить сценарий оболочки в Linux. Вы можете использовать:
bash script.sh
Или вы можете выполнить сценарий оболочки следующим образом:
./script.sh
Это может быть просто, но многого не объясняет. Не волнуйтесь, я сделаю необходимое объяснение с примерами, чтобы вы поняли, почему определенный синтаксис используется в данном формате при запуске сценария оболочки.
Я собираюсь использовать этот однострочный сценарий оболочки, чтобы сделать все как можно проще:
Метод 1. Запуск сценария оболочки путем передачи файла в качестве аргумента оболочки
Первый метод включает передачу имени файла сценария в качестве аргумента в оболочку.
Учитывая, что оболочкой по умолчанию является bash, вы можете запустить такой сценарий:
bash hello.sh
Знаете ли вы преимущество такого подхода? Ваш скрипт не требует разрешения на выполнение. Довольно удобно для быстрых и простых задач.
Имейте в виду, что это должен быть сценарий оболочки, который вы передаете в качестве аргумента. Сценарий оболочки состоит из команд. Если вы используете обычный текстовый файл, он будет жаловаться на неправильные команды.
При таком подходе вы явно указали, что хотите использовать bash в качестве интерпретатора для сценария.
Shell — это просто программа, а bash — ее реализация. Существуют и другие программы-оболочки, такие как ksh, ЗШи т. д. Если у вас установлены другие оболочки, вы также можете использовать их вместо bash.
Например, я установил zsh и использовал его для запуска того же скрипта:
Метод 2: выполнить сценарий оболочки, указав его путь
Другой способ запустить сценарий оболочки — указать путь к нему. Но для этого ваш файл должен быть исполняемым. В противном случае при попытке выполнить сценарий вы получите сообщение об ошибке «В разрешении отказано».
Итак, сначала вам нужно убедиться, что ваш скрипт имеет разрешение на выполнение. Вы можете используйте команду chmod чтобы дать себе это разрешение вот так:
chmod u+x script.sh
После того, как ваш скрипт станет исполняемым, все, что вам нужно сделать, это ввести имя файла вместе с его абсолютным или относительным путем. Чаще всего вы находитесь в одном каталоге, поэтому используйте его так:
./script.sh
Если вы находитесь не в том же каталоге, что и ваш скрипт, вы можете указать ему абсолютный или относительный путь к скрипту:
Это ./ перед скриптом важно (когда вы находитесь в том же каталоге, что и скрипт)
Почему вы не можете использовать имя сценария, когда находитесь в том же каталоге? Это связано с тем, что ваши системы Linux ищут исполняемые файлы для запуска в нескольких выбранных каталогах, указанных в переменной PATH.
Вот значение переменной PATH для моей системы:
Это означает, что любой файл с разрешениями на выполнение в одном из следующих каталогов может быть выполнен из любого места в системе:
- /home/абхишек/.local/bin
- / usr / местные / sbin
- / USR / местные / бен
- / usr / sbin
- / USR / бен
- / SBIN
- / бен
- / usr / игры
- / usr / местные / игры
- / оснастка / bin
Бинарные или исполняемые файлы для команд Linux, таких как ls, cat и т. Д., Находятся в одном из этих каталогов. Вот почему вы можете запускать эти команды из любой точки вашей системы, просто используя их имена. Смотрите, команда ls находится в каталоге / usr / bin.
Когда вы указываете сценарий БЕЗ абсолютного или относительного пути, он не может найти его в каталогах, указанных в переменной PATH.
Почему большинство сценариев оболочки содержат #! / bin / bash в начале сценариев оболочки?
Помните, как я упоминал, что оболочка — это просто программа, и существуют разные реализации оболочек.
Когда вы используете #! / bin / bash, вы указываете, что сценарий должен запускаться с bash в качестве интерпретатора. Если вы этого не сделаете и запустите сценарий в стиле ./script.sh, он обычно запускается с любой запущенной вами оболочкой.
Это имеет значение? Это могло бы. Видите ли, большая часть синтаксиса оболочки является общей для всех типов оболочки, но некоторые могут отличаться.
Например, поведение массива отличается в оболочках bash и zsh. В zsh индекс массива начинается с 1 вместо 0.
С помощью #! / bin / bash указывает, что сценарий является сценарием оболочки bash и должен запускаться с bash в качестве интерпретатора независимо от оболочки, которая используется в системе. Если вы используете специфический для zsh синтаксис, вы можете указать, что это скрипт zsh, добавив #! / bin / zsh в качестве первой строки скрипта.
Пробел между #! / bin / bash не имеет значения. Вы также можете использовать #! / Bin / bash.
Было ли это полезно?
Я надеюсь, что эта статья расширила ваши знания о Linux. Если у вас остались вопросы или предложения, оставьте, пожалуйста, комментарий.
Опытные пользователи все еще могут придраться к этой статье о том, что я пропустил. Но проблема с такими темами для начинающих заключается в том, что нелегко найти правильный баланс информации и избежать слишком большого или слишком малого количества деталей.
Если вы заинтересованы в изучении сценария bash, у нас есть вся серия Bash для начинающих на нашем веб-сайте, ориентированном на системных администраторов Справочник по Linux. Если вы хотите, вы также можете приобретите электронную книгу с дополнительными упражнениями для поддержки Linux Handbook.
Похожие посты:
- Как установить программное обеспечение из исходного кода . и затем удалить его
- Как добавить каталог в PATH в Linux [Быстрый совет]
- Как использовать speedtest на сервере Linux для проверки, хранения и передачи интернет-скорости графически
- Быстрое удаление сложных каталогов
- 20 + бесплатные книги для изучения Linux бесплатно
- 20 + бесплатные электронные книги для изучения Linux бесплатно
- Как создать псевдонимы Bash
- CloudBerry Backup для Linux: настройка и запуск резервного копирования для защиты ваших данных
- Как создать пользовательские команды в Linux
- Запуск вашего первого пакетного скрипта на Windows 10
Источник: websetnet.net