Количество выполненных инструкций для программы Hello World Nasm Assembly и C

У меня есть простой отладчик (с помощью ptrace: http://pastebin.com/D0um3bUi), чтобы подсчитать количество инструкций, выполненных для данной исполняемой программы ввода. Он использует режим однократного выполнения ptrace для подсчета инструкций.

Для этого, когда программа 1) исполняемый файл (a.out из gcc main.c) указан как входной сигнал для моего тестового отладчика, он печатает около 100 тыс. по мере выполнения инструкций. Когда я использую параметр -static, он дает 10681 инструкцию.

Теперь в 2) я создаю программу сборки и использую NASM для компиляции и компоновки, а затем, когда этот исполняемый файл передается в качестве ввода отладчиков, он отображает 8 инструкций как счетчик, а apt.

Число инструкций, выполняемых в программе 1), является высоким из-за того, что программа связана с системной библиотекой во время выполнения? используется -статический и который уменьшает счетчик в 1/10 раз. Как я могу обеспечить, чтобы количество команд было указано только для основной функции в программе 1) и как программа 2) сообщает об отладчике?

1)

#include <stdio.h>

int main()
{
    printf("Hello, world!\n");
    return 0;
}    

Я использую gcc для создания исполняемого файла.

2)

; 64-bit "Hello World!" in Linux NASM

global _start            ; global entry point export for ld

section .text
_start:

    ; sys_write(stdout, message, length)

    mov    rax, 1        ; sys_write
    mov    rdi, 1        ; stdout
    mov    rsi, message    ; message address
    mov    rdx, length    ; message string length
    syscall

    ; sys_exit(return_code)

    mov    rax, 60        ; sys_exit
    mov    rdi, 0        ; return 0 (success)
    syscall

section .data
    message: db 'Hello, world!',0x0A    ; message and newline
    length:    equ    $-message        ; NASM definition pseudo-                             

nasm -f elf64 -o main.o -s main.asm
ld -o main main.o

Ответы

Ответ 1

Количество инструкций, выполняемых в программе 1), является высоким из-за ссылки программы на системную библиотеку во время выполнения?

Да, динамические ссылки и файлы запуска CRT (C runtime).

используется -static и который уменьшает счетчик в 1/10.

Итак, просто оставив начальные файлы CRT, которые делают вещи перед вызовом main и после.

Как я могу убедиться, что количество команд - это только основная функция в программе 1) `

Измерить пустой main, а затем вычесть это число из будущих измерений.

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

и как программа 2) сообщает отладчику.

Это потому, что в этой программе нет другого кода. Это не то, что вы каким-то образом помогли отладчику проигнорировать некоторые инструкции, так что вы сделали программу без каких-либо инструкций, которые вы не поместили там сами.

Если вы хотите увидеть, что на самом деле происходит, когда вы запускаете вывод gcc, gdb a.out, b _start, r и одношаговый. Как только вы углубитесь в дерево вызовов, вы сомневаетесь. захотите использовать fin для завершения выполнения текущей функции, так как вы не хотите выполнять однократное выполнение 1 миллион инструкций или даже 10k.

Ответ 2

Питер дал очень хороший ответ, и я собираюсь продолжить с ответом, который является достойным и может получить некоторые отрицательные голоса. При прямой связи с LD или косвенно с GCC точкой входа по умолчанию для исполняемых файлов ELF является метка _start.

В вашем коде NASM используется глобальная метка _start поэтому при _start вашей программы первый код в вашей программе будет инструкцией _start. При использовании GCC вашей программой типичной точкой входа является функция main. Что скрыто от вас, так это то, что ваша C-программа также имеет метку _start но она предоставляется объектами запуска C-среды выполнения.

Теперь возникает вопрос - есть ли способ обойти файлы запуска C, чтобы избежать кода запуска? Технически да, но это опасная территория, которая может привести к неопределенному поведению. Если вы любите приключения, вы можете сказать GCC изменить точку входа вашей программы с помощью -e командной строки -e. Вместо _start мы могли бы сделать нашу точку входа main обходя код запуска C. Поскольку мы -nostartfiles код запуска C, мы также можем обойтись без связывания в коде запуска среды выполнения C с -nostartfiles.

Вы можете использовать эту командную строку для компиляции вашей C-программы:

gcc test.c -e main -nostartfiles

К сожалению, в коде C есть кое-что, что нужно исправить. Обычно при использовании объектов запуска среды выполнения C после инициализации среды выполняется вызов CALL для main. Обычно main выполняет команду RET, которая возвращает код выполнения C. В этот момент среда выполнения C грациозно выходит из вашей программы. RET -nostartfiles возвращаться при -nostartfiles опции -nostartfiles, так что, скорее всего, это будет ошибка. Чтобы обойти это, мы можем вызвать функцию библиотеки C _exit для выхода из нашей программы.

#include <stdio.h>

int main()
{
    printf("Hello, world!\n");
    _exit(0);  /* We exit application here, never reaching the return */

    return 0;
}   

Если вы не опустите указатели фреймов, GCC выпустит несколько дополнительных инструкций для настройки стекового фрейма и его разбивки, но накладные расходы минимальны.

Специальное примечание

Процесс выше, похоже, не работает для статических сборок (опция -static в GCC) со стандартной библиотекой glibc C. Это обсуждается в этом fooobar.com/info/4610/.... Динамическая версия работает, потому что общий объект может зарегистрировать функцию, которая вызывается динамическим загрузчиком для выполнения инициализации. При статическом построении это обычно выполняется средой выполнения C, но мы пропустили эту инициализацию. Из-за этого функции GLIBC, такие как printf могут не работать. Существуют заменяющие библиотеки C, совместимые со стандартами, которые могут работать без инициализации среды выполнения C. Одним из таких продуктов является MUSL.

Установка MUSL в качестве альтернативы GLIBC

В 64-битной Ubuntu эти команды должны собрать и установить 64-битную версию MUSL:

git clone git://git.musl-libc.org/musl
cd musl
./configure --prefix=/usr/local/musl/x86-64
make
sudo make install

Затем вы можете использовать оболочку MUSL для GCC для работы с библиотекой MUSL C вместо библиотеки GLIBC по умолчанию в большинстве дистрибутивов Linux. Параметры аналогичны GCC, поэтому вы должны быть в состоянии сделать:

/usr/local/musl/x86-64/bin/musl-gcc -e main -static -nostartfiles test.c

При запуске ./a.out сгенерированного с помощью GLIBC, он, скорее всего, будет ./a.out MUSL не требует инициализации перед использованием большинства функций библиотеки C, поэтому он должен работать даже с -static GCC.


Более справедливое сравнение

Одна из проблем вашего сравнения заключается в том, что вы вызываете системный вызов SYS_WRITE непосредственно в NASM, в C вы используете printf. Пользователь EOF правильно прокомментировал, что вы можете сделать это более справедливым сравнением, вызвав функцию write в C вместо printf. write имеет гораздо меньше накладных расходов. Вы можете изменить свой код так:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    char *str = "Hello, world\n";
    write (STDOUT_FILENO, str, 13);
    _exit(0);
    return 0;
}

Это будет иметь больше издержек, чем прямой системный вызов SYS_WRITE NASM, но намного меньше, чем то, что генерирует printf.


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