Полное понимание того, как исполняется файл .exe - PullRequest
0 голосов
/ 15 апреля 2020

Цель

Я хочу понять, как работают исполняемые файлы. Я надеюсь, что понимание одного очень конкретного c примера во всех подробностях позволит мне сделать это. Моя последняя (возможно, слишком амбициозная) цель - взять файл hello-world .exe (скомпилированный с помощью компилятора C и связанный с ним) и полностью понять, как он загружается в память и выполняется процессором x86. Если мне это удастся, я хочу написать статью и / или снять видео об этом, так как я не нашел ничего подобного в целых rnet.

Specifi c вопросах, которые я хочу просить отмечены жирным шрифтом. Конечно, любые дальнейшие предложения и источники, делающие что-то подобное, очень приветствуются. Заранее большое спасибо за любую помощь!

Что мне нужно

Этот Ответ дает обзор процесса, через который C код проходит, пока не попадет в физический память как программа. Я еще не уверен, насколько сильно я хочу посмотреть, как компилируется код C. Есть ли способ просмотреть код сборки, который генерирует компилятор C перед сборкой? Я могу решить, что стоит попытаться понять процессы загрузки и компоновки. В то же время наиболее важными частями, которые мне нужно понять, являются

  • формат исполняемого файла PA
  • отношение между кодом ассемблера и байт-кодом x86
  • процесса загрузки (то есть как оперативная память процесса подготовлена ​​к выполнению с использованием информации из исполняемого файла).

У меня очень базовое c понимание формата PA (это понимание будет изложено в раздел «Что я выучил до сих пор»), и я думаю, что приведенные там источники должны быть достаточными, мне просто нужно изучить его еще немного, пока я не узнаю достаточно, чтобы понять основную программу c Hello-World. Дополнительные источники по этой теме c, конечно, очень приветствуются .

Перевод байт-кода в код на ассемблере (дизассемблирование) кажется довольно сложным для x86. Тем не менее, я хотел бы узнать больше об этом. Как вы могли бы go разобрать сегмент короткого байтового кода?

Я все еще ищу способ просмотреть содержимое памяти процесса (назначенной ему виртуальной памяти). Я уже изучил функции windows -kernel32.dll, такие как ReadProcessMemory, но пока не смог заставить его работать. Также мне странно, что для этого нет (бесплатных) инструментов. Вместе с пониманием загрузки я смогу понять, как процесс запускается из ОЗУ. Также я ищу инструменты отладки для программистов на ассемблере, которые позволяют просматривать весь процесс виртуальной памяти. Моя текущая отправная точка этого поиска - это вопрос . Есть ли у вас дальнейшие рекомендации по , как я могу видеть и понимать загрузку и выполнение процесса из ОЗУ?

Что я выучил до сих пор

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

Формат PA

В Windows исполняемый файл соответствует формату PA . Официальная документация и , эта статья дает хороший обзор формата. Формат описывает, что означают отдельные байты в файле .exe. Начало - это программа для DOS (включенная по старым причинам), о которой я не буду беспокоиться. Затем идет группа заголовков, которые дают информацию об исполняемом файле. Фактическое содержимое файла разделено на разделы с именами, например, «.rdata». После заголовков файлов есть также заголовки разделов, которые сообщают вам, какие части файла являются какими разделами и что делает каждый раздел (например, если он содержит исполняемый код).

Заголовки и разделы можно анализировать с помощью инструменты, такие как dumpbin (инструмент Microsoft для просмотра бинарных файлов). Для сравнения с выводом дампа шестнадцатеричный код файла можно просмотреть непосредственно с помощью Hex-редактора или даже с помощью Powershell (команда Format-Hex -Path <Path to file>).

Specifi c пример

I выполнил эти шаги для очень простой программы, которая ничего не делает. Это код:

; NASM assembler programm. Does nothing. Stores string in code section. 
; Adapted from stackoverflow.com/a/1029093/9988487
    global _main
    section .text
_main:
    hlt
    db      'Hello, World'

Я собрал его с помощью NASM (команда nasm -fwin32 filename.asm) и связал его с компоновщиком, который поставляется с VS2019 (link /subsystem:console /nodefaultlib /entry:main test.obj). Это адаптировано из этого ответа , который демонстрирует, как создать программу hello-world для Windows с использованием вызова WinAPI. Программа работает на Windows 10 и завершается без вывода. Для запуска требуется около 2 сек c, что кажется очень длинным и заставляет меня думать может быть какая-то ошибка где-то ?

Затем я посмотрел на свалку вывод:

D:\ASM>dumpbin test.exe /ALL
Microsoft (R) COFF/PE Dumper Version 14.22.27905.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file test.exe

PE signature found

File Type: EXECUTABLE IMAGE

FILE HEADER VALUES
             14C machine (x86)
               2 number of sections
        5E96C000 time date stamp Wed Apr 15 10:04:16 2020
               0 file pointer to symbol table
               0 number of symbols
              E0 size of optional header
             102 characteristics
                   Executable
                   32 bit word machine

OPTIONAL HEADER VALUES
             10B magic # (PE32)
           14.22 linker version
             200 size of code
             200 size of initialized data
               0 size of uninitialized data
            1000 entry point (00401000)
            1000 base of code
            2000 base of data
          400000 image base (00400000 to 00402FFF)
            1000 section alignment
             200 file alignment
            <further header values omitted ...>

SECTION HEADER #1
   .text name
       E virtual size
    1000 virtual address (00401000 to 0040100D)
     200 size of raw data
     200 file pointer to raw data (00000200 to 000003FF)
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
60000020 flags
         Code
         Execute Read

RAW DATA #1
  00401000: F4 48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 0A        ôHello, World.

SECTION HEADER #2
  .rdata name
      58 virtual size
    2000 virtual address (00402000 to 00402057)
     200 size of raw data
     400 file pointer to raw data (00000400 to 000005FF)
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
40000040 flags
         Initialized Data
         Read Only

RAW DATA #2
  00402000: 00 00 00 00 00 C0 96 5E 00 00 00 00 0D 00 00 00  .....À.^........
  00402010: 3C 00 00 00 1C 20 00 00 1C 04 00 00 00 00 00 00  <.... ..........
  00402020: 00 10 00 00 0E 00 00 00 2E 74 65 78 74 00 00 00  .........text...
  00402030: 00 20 00 00 1C 00 00 00 2E 72 64 61 74 61 00 00  . .......rdata..
  00402040: 1C 20 00 00 3C 00 00 00 2E 72 64 61 74 61 24 7A  . ..<....rdata$z
  00402050: 7A 7A 64 62 67 00 00 00                          zzdbg...

  Debug Directories

        Time Type        Size      RVA  Pointer
    -------- ------- -------- -------- --------
    5E96C000 coffgrp       3C 0000201C      41C

  Summary
        1000 .rdata
        1000 .text

Поле заголовка файла «характеристики» представляет собой комбинацию флагов. В частности, 102h = 1 0000 0010b и два установленных флага (в соответствии с форматом PE делают c): IMAGE_FILE_EXECUTABLE_IMAGE и IMAGE_FILE_BYTES_REVERSED_HI. Последний имеет описание

IMAGE_FILE_BYTES_REVERSED_HI:
Big endian: MSB предшествует LSB в памяти. Этот флаг устарел и должен быть нулевым.

Я спрашиваю себя: Почему современный ассемблер и современный компоновщик создают устаревший флаг?

В файле 2 раздела. Раздел .text был определен в коде ассемблера (и является единственным, содержащим исполняемый код, как указано в его заголовке). Я не знаю, что это за второй раздел «.rdata» (имя, по-видимому, относится к «читаемым данным»). Почему он был создан? Как я могу узнать?

Разборка

Я использовал dumpbin для дизассемблирования файла .exe (команда dumpbin test.exe /DISASM). Он получает hlt правильно, «Привет, мир». Строка (возможно, к сожалению) интерпретируется как исполняемые команды. Я думаю, что дизассемблер вряд ли можно обвинить в этом. Однако, если я правильно понимаю (у меня нет практического опыта в программировании на ассемблере), размещение данных в разделе кода не является неслыханным (это было сделано в нескольких примерах, которые я обнаружил, изучая программирование на ассемблере). Есть ли лучший способ разобрать это, чтобы лучше воспроизвести мой ассемблерный код ? Кроме того, компиляторы иногда помещают данные в секции кода таким образом?

Ответы [ 3 ]

2 голосов
/ 15 апреля 2020

В некоторых отношениях это очень широкий вопрос, который может не выжить по этой причине.

Вся информация находится в inte rnet, продолжайте искать, это не сложно, не достойно бумага или видео. (или вопрос stackoverflow?)

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

Затем существуют форматы файлов, и есть много разных, для которых мы все используем термин «двоичный», но опять же, разные форматы. В идеале они содержат, с использованием какой-либо формы кодирования, машинный код и данные или информацию о данных.

Пока что будем использовать ARM, инструкции фиксированной длины, легко разбираемые и читаемые, и т. Д. c.

#define ONE 1
unsigned int x;
unsigned int y = 5;
const unsigned int z = 7;
unsigned int fun ( unsigned int a )
{
    return(a+ONE);
}

и gnu gcc / binutils, поскольку он очень хорошо известен, широко используется, его можно использовать для создания программ на компьютере wintel. Я запускаю linux, так что вы увидите, что elf не exe, но это просто формат файла для того, что вы просите.

arm-none-eabi-gcc -O2 -c so.c -save-temps -o so.o

Этот набор инструментов (цепочка инструментов, которые связаны, например, компилятор -> ассемблер -> компоновщик) unix стиль и модульность. У вас будет ассемблер для цели, поэтому вы не знаете, зачем вам это изобретать, и отладку компилятора гораздо проще посмотреть на вывод сборки, чем пытаться go перейти прямо к машинному коду. Но есть люди, которые любят взбираться на гору только потому, что она есть, а не go вокруг, а некоторые инструменты go прямо для машинного кода только потому, что он есть.

этот специфический c компилятор имеет это Функция сохранения временных параметров, g cc сама по себе является программой переднего плана, которая готовит настоящий компилятор, а затем, если ее попросят (если вы не говорите, нет), вызовет ассемблер и компоновщик.

cat so.i
# 1 "so.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "so.c"


unsigned int x;
unsigned int y = 5;
const unsigned int z = 7;
unsigned int fun ( unsigned int a )
{
    return(a+1);
}

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

компилятор делает свое дело и превращает его в язык ассемблера

cat so.s
    .cpu arm7tdmi
    .eabi_attribute 20, 1
    .eabi_attribute 21, 1
    .eabi_attribute 23, 3
    .eabi_attribute 24, 1
    .eabi_attribute 25, 1
    .eabi_attribute 26, 1
    .eabi_attribute 30, 2
    .eabi_attribute 34, 0
    .eabi_attribute 18, 4
    .file   "so.c"
    .text
    .align  2
    .global fun
    .arch armv4t
    .syntax unified
    .arm
    .fpu softvfp
    .type   fun, %function
fun:
    @ Function supports interworking.
    @ args = 0, pretend = 0, frame = 0
    @ frame_needed = 0, uses_anonymous_args = 0
    @ link register save eliminated.
    add r0, r0, #1
    bx  lr
    .size   fun, .-fun
    .global z
    .global y
    .comm   x,4,4
    .section    .rodata
    .align  2
    .type   z, %object
    .size   z, 4
z:
    .word   7
    .data
    .align  2
    .type   y, %object
    .size   y, 4
y:
    .word   5
    .ident  "GCC: (GNU) 9.3.0"

, который затем получает положить в объектный файл, в данном случае binutils, linux default, et c

file so.o
so.o: ELF 32-bit LSB relocatable, ARM, EABI5 version 1 (SYSV), not stripped

он использует формат файла elf, который легко найти информацию, легко написать программы для parse, et c.

Я могу разобрать это, обратите внимание, что, поскольку я использую дизассемблер, он пытается разобрать все, даже если это не машинный код, придерживаясь 32-битной руки ff Это можно перевернуть, и когда есть реальные инструкции, они показываются (выровненные, а не переменной длины, как здесь используется, так что вы можете разбирать линейно, чего вы не можете с набором команд переменной длины, и иметь надежду на успех (например, x86). необходимо разобрать в порядке выполнения, а затем вы часто пропускаете некоторые из-за характера программы)

arm-none-eabi-objdump -D so.o

so.o:     file format elf32-littlearm


Disassembly of section .text:

00000000 <fun>:
   0:   e2800001    add r0, r0, #1
   4:   e12fff1e    bx  lr

Disassembly of section .data:

00000000 <y>:
   0:   00000005    andeq   r0, r0, r5

Disassembly of section .rodata:

00000000 <z>:
   0:   00000007    andeq   r0, r0, r7

Disassembly of section .comment:

00000000 <.comment>:
   0:   43434700    movtmi  r4, #14080  ; 0x3700
   4:   4728203a            ; <UNDEFINED> instruction: 0x4728203a
   8:   2029554e    eorcs   r5, r9, lr, asr #10
   c:   2e332e39    mrccs   14, 1, r2, cr3, cr9, {1}
  10:   Address 0x0000000000000010 is out of bounds.


Disassembly of section .ARM.attributes:

00000000 <.ARM.attributes>:
   0:   00002941    andeq   r2, r0, r1, asr #18
   4:   61656100    cmnvs   r5, r0, lsl #2
   8:   01006962    tsteq   r0, r2, ror #18
   c:   0000001f    andeq   r0, r0, pc, lsl r0
  10:   00543405    subseq  r3, r4, r5, lsl #8
  14:   01080206    tsteq   r8, r6, lsl #4
  18:   04120109    ldreq   r0, [r2], #-265 ; 0xfffffef7
  1c:   01150114    tsteq   r5, r4, lsl r1
  20:   01180317    tsteq   r8, r7, lsl r3
  24:   011a0119    tsteq   r10, r9, lsl r1
  28:   Address 0x0000000000000028 is out of bounds.

и да, инструмент добавляет туда дополнительные вещи, но обратите внимание прежде всего на то, что я создал. некоторый код, некоторые инициализированные данные для чтения / записи, некоторые инициализированные данные для чтения / записи и некоторые инициализированные данные только для чтения. Авторы цепочки инструментов могут использовать любые имена, которые им нужны, им даже не нужно использовать термин section. Но из десятилетий истории и коммуникации и терминологии .text обычно используется для кода (как в машинном коде и данных, связанных только для чтения), .bss для обнуленных данных чтения / записи, хотя я видел другие имена, .data для инициализированного чтения / записи данные и это поколение этого инструмента .rodata для инициализированных данных только для чтения (технически это может быть в .text)

И обратите внимание, что все они имеют нулевой адрес. они еще не связаны.

Теперь это уродливо, но чтобы не добавлять больше кода, а инструмент позволяет мне сделать это, позволяет связать его для создания совершенно непригодного двоичного файла (нет bootstrap, et c, et c)

arm-none-eabi-ld -Ttext=0x1000 -Tdata=0x2000 so.o -o so.elf
arm-none-eabi-ld: warning: cannot find entry symbol _start; defaulting to 0000000000001000

arm-none-eabi-objdump -D so.elf

so.elf:     file format elf32-littlearm


Disassembly of section .text:

00001000 <fun>:
    1000:   e2800001    add r0, r0, #1
    1004:   e12fff1e    bx  lr

Disassembly of section .data:

00002000 <y>:
    2000:   00000005    andeq   r0, r0, r5

Disassembly of section .rodata:

00001008 <z>:
    1008:   00000007    andeq   r0, r0, r7

Disassembly of section .bss:

00002004 <x>:
    2004:   00000000    andeq   r0, r0, r0

А теперь это связано. Элементы только для чтения .text и .rodata находятся в адресном пространстве .text в порядке, указанном в файле. Элементы чтения / записи помещались в адресное пространство .data в порядке, указанном в файле.

yes где был. бсс в объекте? Он там, у него нет фактических данных, как в байтах, которые являются частью объекта, вместо этого у него есть имя и размер, и он является .bss. И по любой причине инструмент показывает его из связанного двоичного файла.

Итак, вернемся к термину двоичный. Двоичный файл so.elf содержит байты go в памяти, составляющие программу, а также инфраструктуру форматов файлов и таблицу символов для облегчения разборки и отладки, а также другие элементы. Elf - это гибкий формат файла, который GNU может использовать, и вы получите один результат, который может использовать другой инструмент или версия инструмента и получить другой файл. И очевидно, что два компилятора могут генерировать различный машинный код из одной и той же исходной программы не только из-за оптимизации, задача состоит в том, чтобы сделать функциональную программу на целевом языке, а функциональность - по мнению автора компилятора / инструмента.

как насчет файла типа образа памяти:

arm-none-eabi-objcopy so.elf so.bin -O binary
hexdump -C so.bin
00000000  01 00 80 e2 1e ff 2f e1  07 00 00 00 00 00 00 00  |....../.........|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00001000  05 00 00 00                                       |....|
00001004

Теперь, как работает инструмент objcopy, он начинается с первого определенного загружаемого или любого другого термина, который вы хотите использовать, и заканчивается последним и использует (ноль) ) заполнение, чтобы размер файла совпадал, чтобы изображение памяти совпадало с точки зрения адреса. Звездочка означает, по существу, 0 отступов. Потому что мы начали с 0x1000 с .text и 0x2000 для .data, но первый байт этого файла (смещение 0) - это начало .text и 0x1000 байт позже, который смещен в файл 0x1000, но мы знаем, что он переходит к 0x2000 в памяти это материал для чтения / записи. Также обратите внимание, что нули bss отсутствуют в выводе. ожидается, что bootstrap обнулит их.

Нет информации, например, где в памяти находятся эти данные из этого файла и т. Д. c. И если вы немного об этом думаете, что если у меня есть один байт в разделе, который я определяю, идет в 0x00000000 и один байт в разделе, который я определяю, идет в 0x80000000 и выводит этот файл, да, это файл байтов 0x80000001, хотя есть только два полезных байта соответствующей информации. Файл 2 ГБ для хранения двух байтов. Вот почему вы не хотите выводить этот формат файла, пока не разберетесь со сценарием и инструментами компоновщика.

Те же данные и два других одинаково старых школьных формата с небольшой историей Intel против Motorola

arm-none-eabi-objcopy so.elf so.hex -O ihex
cat so.hex
:08100000010080E21EFF2FE158
:0410080007000000DD
:0420000005000000D7
:0400000300001000E9
:00000001FF

arm-none-eabi-objcopy so.elf so.srec -O srec
cat so.srec
S00A0000736F2E7372656338
S10B1000010080E21EFF2FE154
S107100807000000D9
S107200005000000D3
S9031000EC

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

И бесчисленное множество других форматов двоичных файлов и инструмент, такой как objdump, имеет приличный список форматов, которые он может генерировать также как и другие компоновщики и / или инструменты там.

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

Какой формат и какие адреса вы можете спросить ... Это часть операционной системы или системы дизайна. В случае Windows существуют определенные c форматы файлов и варианты, возможно, тех форматов, которые поддерживаются операционной системой windows, используемой вами версией c. Windows определил, как выглядит адресное пространство. Подобные операционные системы используют MMU как для виртуализации адресов, так и для защиты. Наличие виртуального адресного пространства означает, что каждая программа может жить в одном и том же пространстве. Все программы могут иметь нулевой адрес, например ....

test. c

int main ( void )
{
    return 1;
}

привет. c

int main ( void )
{
    return 2;
}


gcc test.c -o test
objdump -D test

Disassembly of section .text:

00000000004003e0 <_start>:
  4003e0:   31 ed                   xor    %ebp,%ebp
  4003e2:   49 89 d1                mov    %rdx,%r9
  4003e5:   5e                      pop    %rsi
...

gcc hello.c -o hello
objdump -D hello

Disassembly of section .text:

00000000004003e0 <_start>:
  4003e0:   31 ed                   xor    %ebp,%ebp
  4003e2:   49 89 d1                mov    %rdx,%r9

тот же адрес, как это возможно, если они не сидят друг на друге? нет виртуальной машины. И обратите внимание, что он создан для определенного c linux в указанный c день и т. Д. c. У цепочки инструментов есть скрипт компоновщика по умолчанию (заметьте, я не указывал, как связывать) для этой платформы, когда компилятор был создан для этой цели / платформы.

arm-none-eabi-gcc -O2 test.c -c -o test.o
arm-none-eabi-ld test.o -o test.elf
arm-none-eabi-ld: warning: cannot find entry symbol _start; defaulting to 0000000000008000
arm-none-eabi-objdump -D test.elf

test.elf:     file format elf32-littlearm


Disassembly of section .text:

00008000 <main>:
    8000:   e3a00001    mov r0, #1
    8004:   e12fff1e    bx  lr

один и тот же исходный код, тот же компилятор, созданный для другой цели и другого системного адреса.

Так что для windows определенно будут правила для поддерживаемых двоичных форматов и правила для адресных пространств, которые можно использовать, как определять эти пространства в файле.

Тогда это просто вопрос запуска операционной системы для чтения двоичного файла и помещения загружаемых элементов в память по этим адресам (в виртуальном пространстве, которое ОС создала для этой конкретной c программы). Вполне возможно, что функция загрузчика на ноль BSS для вас, так как информация там. Программист низкого уровня должен знать, что, возможно, иметь дело с обнулением .bss или нет.

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

.globl _start
_start:
    ldr sp,sp_init
    bl fun
    b .

.word __bss_start__
.word __bss_end__

sp_init:
.word 0x8000

все, что касается языка ассемблера, - это спецификация инструмента c, мнемоника по соображениям здравого смысла, без сомнения, будет напоминать документацию поставщиков IP / процессоров, в которой используется синтаксис, который использует инструмент, который они заплатили за разработку. Но помимо этого ассемблера язык полностью определяется инструментом, а не целью, x86 из-за своего возраста и прочих обстоятельств действительно плох в этом, и это не Intel против AT & T, в общем. Ассемблер Gnu хорошо известен тем, что я предположил бы, возможно, намеренно не создание совместимых языков с другими языками ассемблера. Выше приведен gnu-ассемблер для arm

с использованием функции fun (), приведенной выше, C говорит, что это должно быть main (), но инструменту все равно, я уже достаточно набрал здесь.

add простой скрипт компоновщика на базе оперативной памяти

MEMORY
{
    ram : ORIGIN = 0x1000, LENGTH = 0x1000
}

SECTIONS
{
    .text : { *(.text*) } > ram
    .rodata : { *(.rodata*) } > ram
    .bss : {
        __bss_start__ = .;
        *(.bss*)
     } > ram
        __bss_end__ = .;
}

построить все это

arm-none-eabi-as start.s -o start.o
arm-none-eabi-gcc -O2 -c so.c -o so.o
arm-none-eabi-ld -T sram.ld start.o so.o -o so.elf

исследовать

arm-none-eabi-nm so.elf
0000102c B __bss_end__
00001028 B __bss_start__
00001018 T fun
00001014 t sp_init
00001000 T _start
00001028 B x
00001024 D y
00001020 R z


arm-none-eabi-objdump -D so.elf

so.elf:     file format elf32-littlearm


Disassembly of section .text:

00001000 <_start>:
    1000:   e59fd00c    ldr sp, [pc, #12]   ; 1014 <sp_init>
    1004:   eb000003    bl  1018 <fun>
    1008:   eafffffe    b   1008 <_start+0x8>
    100c:   00001028    andeq   r1, r0, r8, lsr #32
    1010:   0000102c    andeq   r1, r0, r12, lsr #32

00001014 <sp_init>:
    1014:   00008000    andeq   r8, r0, r0

00001018 <fun>:
    1018:   e2800001    add r0, r0, #1
    101c:   e12fff1e    bx  lr

Disassembly of section .rodata:

00001020 <z>:
    1020:   00000007    andeq   r0, r0, r7

Disassembly of section .data:

00001024 <y>:
    1024:   00000005    andeq   r0, r0, r5

Disassembly of section .bss:

00001028 <x>:
    1028:   00000000    andeq   r0, r0, r0

так что теперь можно добавить к bootstrap a Обнуление памяти l oop (не используйте C / memset, вы не создаете проблемы с курицей и яйцами, вы пишете bootstrap в asm) на основе начального и конечного адресов.

к счастью или к сожалению, потому что скрипт компоновщика это спецификация инструмента c, а язык ассемблера это спецификация инструмента c, и они должны работать вместе, если вы позволяете инструментам делать работу за вас (нормальный способ сделать это, получайте удовольствие, выясняя, где .bss иначе) ,

это можно сделать в операционной системе, но когда вы попадаете, скажем, в микроконтроллеры, где все это должно быть в энергонезависимой памяти (fla sh), вполне возможно, что она будет загружена из другого места (например, иногда ваша прошивка мыши, иногда клавиатура и т. д. c) в оперативную память, предположим, что fla sh, так как вы справляетесь с .data ??

MEMORY
{
    rom : ORIGIN = 0x0000, LENGTH = 0x1000
    ram : ORIGIN = 0x1000, LENGTH = 0x1000
}
SECTIONS
{
    .text : { *(.text*) } > rom
    .rodata : { *(.rodata*) } > rom
    .data : {
        *(.data*)
     } > ram AT > rom
    .bss : {
        __bss_start__ = .;
        *(.bss*)
     } > ram
        __bss_end__ = .;
}

с GNU LD Это в основном говорит, что .data home находится в ram, но выходные двоичные форматы поместят его во flash / rom

so.elf so.srec -O srec
cat so.srec
S00A0000736F2E7372656338
S11300000CD09FE5030000EBFEFFFFEA04100000A4
S11300100810000000800000010080E21EFF2FE1B4
S107002007000000D1  <-  z variable at address 0020
S107002405000000CF  <-  y variable at 0024
S9030000FC

, и вам придется поиграться со скриптом компоновщика, чтобы получить инструмент, который сообщит вам как ram, так и fla sh начальные адреса и конечные адреса или длина. затем добавьте код в bootstrap (asm not C), чтобы скопировать .data из fla sh в ram.

Также обратите внимание здесь на еще один из ваших многочисленных вопросов.

.word __bss_start__
.word __bss_end__

sp_init:
.word 0x8000

эти пункты являются технически данными. но они живут в .text прежде всего потому, что они были определены в коде, который предположительно был .text (мне не нужно было указывать это в asm, но мог бы иметь). вы также увидите это в x86, но для фиксированной длины, такой как arm, mips, ris c -v, et c, где вы не можете поместить любое старое немедленное / постоянное / связанное значение, которое вы хотите, в саму инструкцию, которую вы помещаете. поблизости в "пуле" и сделайте ap c относительное чтение, чтобы получить его. Вы увидите это и для связи с внешними объектами: переменная

extern unsigned int x;
int main ( void )
{
    return x;
}


arm-none-eabi-gcc -O2 -c test.c -o test.o
arm-none-eabi-objdump -D test.o

test.o:     file format elf32-littlearm


Disassembly of section .text.startup:

00000000 <main>:
   0:   e59f3004    ldr r3, [pc, #4]    ; c <main+0xc>
   4:   e5930000    ldr r0, [r3]
   8:   e12fff1e    bx  lr
   c:   00000000    andeq   r0, r0, r0   <--- the code gets the address of the

отсюда, а затем считывает ее из памяти

после привязки

Disassembly of section .text:

00008000 <main>:
    8000:   e59f3004    ldr r3, [pc, #4]    ; 800c <main+0xc>
    8004:   e5930000    ldr r0, [r3]
    8008:   e12fff1e    bx  lr
    800c:   00018010    andeq   r8, r1, r0, lsl r0

Disassembly of section .data:

00018010 <x>:
   18010:   00000005    andeq   r0, r0, r5

для x86

gcc -c -O2 test.c -o test.o
dwelch-desktop so # objdump -D test.o

test.o:     file format elf64-x86-64


Disassembly of section .text.startup:

0000000000000000 <main>:
   0:   8b 05 00 00 00 00       mov    0x0(%rip),%eax        # 6 <main+0x6>
   6:   c3                      retq   


00000000004003e0 <main>:
  4003e0:   8b 05 4a 0c 20 00       mov    0x200c4a(%rip),%eax        # 601030 <x>
  4003e6:   c3                      retq   

Если вы косите, это действительно по-другому? поблизости есть данные, которые процессор читает для загрузки в регистр и / или использования. в любом случае, из-за природы набора команд компоновщик изменяет инструкцию или данные ближайшего пула, или и то и другое.

последний:

arm-none-eabi-gcc -S test.c 
cat test.s 
    .cpu arm7tdmi
    .eabi_attribute 20, 1
    .eabi_attribute 21, 1
    .eabi_attribute 23, 3
    .eabi_attribute 24, 1
    .eabi_attribute 25, 1
    .eabi_attribute 26, 1
    .eabi_attribute 30, 6
    .eabi_attribute 34, 0
    .eabi_attribute 18, 4
    .file   "test.c"
    .text
    .align  2
    .global main
    .arch armv4t
    .syntax unified
    .arm
    .fpu softvfp
    .type   main, %function
main:
    @ Function supports interworking.
    @ args = 0, pretend = 0, frame = 0
    @ frame_needed = 1, uses_anonymous_args = 0
    @ link register save eliminated.
    str fp, [sp, #-4]!
    add fp, sp, #0
    ldr r3, .L3
    ldr r3, [r3]
    mov r0, r3
    add sp, fp, #0
    @ sp needed
    ldr fp, [sp], #4
    bx  lr
.L4:
    .align  2
.L3:
    .word   x
    .size   main, .-main
    .ident  "GCC: (GNU) 9.3.0"

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

если у вас есть данные в коде, да, есть моменты и причины для того, чтобы значения данных были в .text область не просто целевая спецификация c, вы увидите это по разным причинам, и некоторые наборы инструментов помещают туда данные только для чтения.

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

Формат файла и пространство памяти для программы определяются операционной системой c, а не языком или даже указанием цели c (linux, windows, макросы на том же ноутбуке не должны иметь одинаковый ru файлы, несмотря на тот же целевой компьютер). У нативного набора инструментов для этой платформы есть скрипт компоновщика по умолчанию и любая другая информация, необходимая для создания работоспособных / работающих программ для этой цели. Включая поддерживаемый формат файла.

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

Программы имеют ошибки и нюансы. Форматы файлов имеют версии и несоответствия, вы можете найти какой-нибудь эльфийский ридер форматов файлов только для того, чтобы обнаружить, что он не работает или распечатывает странные вещи при подаче очень хорошего эльфийского файла, который работает в некоторой системе. Почему устанавливаются некоторые флаги? Возможно, эти байты были использованы повторно, или был изменен флаг, или структура данных изменилась, или инструмент использует их по-другому или нестандартным образом (например, mov 20h, ax), а другой несовместимый инструмент не может понять или повезло. и подбирается достаточно близко.

Если спросить, почему вопросы в stackoverflow не очень полезны, то шансы найти человека, написавшего эту вещь, очень и очень низки, больше шансов спросить место, откуда вы взяли инструмент, и проследить, чтобы человек все еще надеялся жив и готов быть обеспокоенным. И 99,999 (много по 9 с)% не существует глобального набора божественных правил, для которых эта вещь была написана под / для. В общем, какой-то чувак просто почувствовал, что именно поэтому они сделали то, что сделали, без реальной причины, лени, ошибки, намеренно пытаясь сломать чей-то инструмент. Вплоть до большого комитета людей с мнением, проголосовавшим за него в определенный день в определенной комнате, и вот почему. (и мы знаем, что получаем, когда проектируем по комитетам или пытаемся написать спецификации, которым никто не соответствует)

Я знаю, что вы работаете на windows, и у меня нет машины windows под рукой и я Linux. Но инструменты gnu / binutils и clang / llvm легко доступны и имеют богатый набор инструментов, таких как readelf, nm, objdump и т. Д. c. Это помогает при изучении вещей, хороший инструмент будет иметь это, по крайней мере, для разработчиков, чтобы они могли отладить вывод инструмента до определенного уровня качества. Люди из GNU создали инструменты и сделали их доступными для всех, и хотя для их разбора и их функций требуется время, они очень важны для вещей, которые вы пытаетесь понять.

Вы НЕ найдете хорошего x86 дизассемблер, все они чушь просто из-за природы зверя. Это набор команд переменной длины, поэтому по определению, если вы не выполняете его, вы не можете правильно его отсортировать. Вы должны дизассемблировать в порядке выполнения из известной хорошей точки входа, чтобы иметь половину шансов, и затем по разным причинам существуют пути кода, которые вы не можете увидеть таким образом (например, подумайте о таблицах переходов или файлах dll или около того). ЛУЧШЕЕ решение - иметь очень точный / совершенный эмулятор / симулятор, запускать код и выполнять все необходимые действия / движения, чтобы он покрывал все пути кода, и иметь этот инструмент для записи инструкций из данных и того, где каждый находится или каждая линейная секция без ветви.

Хорошая сторона этого в том, что сегодня много кода скомпилировано с использованием инструментов, которые не пытаются ничего скрыть. В старые времена по разным причинам вы видели рукописный ассм, который намеренно пытался предотвратить разборку или из-за других факторов (ручное редактирование бинарного образа для видеоигры за день до выставки, go разбирать некоторые классы c roms).

mov r0,#0
cmp r0,#0
jz somewhere
.word 0x12345678

дизассемблер не собирается это выяснять, некоторые могут добавить для этого случай, тогда

mov r0,#0
nop
nop
xor r0,#1
nop
nop
xor r0,#3
xor r0,#2
cmp r0,#0
jz somewhere
.word 0x12345678

и он думает, что данные - это инструкция, для переменной длины, которая является очень трудной для дизассемблера, разрешить приличную проблему, по крайней мере, обнаружит коллизии, где часть команды без кода операции разветвлена, и / или часть кода операции кода обнаруживается позже как дополнительные байты в некоторой другой инструкции , Инструмент не может разрешить это человеку.

Даже с рукой и мипсом и с 32- и 16-битными инструкциями, ris c -v с инструкциями переменного размера, et c ...

очень часто дизассемблер GNU срабатывает с x86.

1 голос
/ 05 мая 2020

Ответы на вопросы в вашем тексте: 1. Вы можете увидеть пошаговое выполнение процесса и обработать память с помощью отладчика. Я использовал OllyDbg для изучения ассемблера, это бесплатный и мощный отладчик. 2. Процесс загружается ядром Windows после вызова NtCreateUserProcess, поэтому я думаю, что вам потребуется отладка ядра, чтобы увидеть, как это делается. 3. Код, отлаженный в OllyDbg, автоматически разбирается. 4. Вы можете поместить данные только для чтения в раздел «.text». Вы можете изменить флаги раздела, чтобы сделать его доступным для записи, тогда код и данные могут быть смешаны. Некоторые компиляторы могут объединять разделы «.text» и «.rdata».

Я бы порекомендовал вам прочитать об импорте, экспорте, перемещениях и ресурсах PE в этом порядке. Если вы хотите увидеть самый простой из возможных helloworld i386 PE, вы можете проверить мою программу hello_world_pe_i386_dynami c .exe здесь: https://github.com/pajacol/hello-world. Я написал это полностью в редакторе двоичных файлов. Он содержит только необходимые структуры данных. Этот исполняемый файл не зависит от позиции и может быть загружен по любому адресу без перемещения.

1 голос
/ 15 апреля 2020

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

Нет, компиляторы не помещают данные в разделы кода (поправьте меня, если я ошибаюсь). Есть раздел .data (для инициализированных данных) и раздел .bss (для неинициализированных данных).

Думаю, я лучше покажу вам пример программы, которая печатает hello world (для linux) потому что это намного проще, и я не знаю, как поступить с windows. в x64, но это как x86. Только имена системных вызовов и регистров, которые отличаются. x64 для 64 бит и x86 для 32 бит).

BITS 64 ;not obligatory but I prefer

section .data
msg db "hello world" ;the message
len equ $-msg ;the length of msg

section .text
global _start

_start: ;the entry point
mov rax, 1 ;syscall 1 to print something
mov rdi, 1 ;1 for stdout
mov rsi, msg ;the message 
mov rdx, len ;length in rdx
syscall

mov rax, 60 ;exit syscall
mov rdi, 0 ;exit with 0
syscall

(https://tio.run/#assembly -nasm , если вы не хотите использовать виртуальную машину. Я советую вам искать WSL + vscode, если вы используете windows. будет linux в вашем windows, а vscode имеет расширение для доступа к файлам в windows), но

Если вы хотите разобрать код или посмотреть, что такое память, вы можете использовать GDB или Radare2 в linux. Для windows существуют другие инструменты, такие как ghidra, IDA, olly dbg ..

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

Я никогда ничего не делал для windows. Однако, чтобы связать мой объектный файл, я использую ld (я не знаю, будет ли он полезен).

ld object.o -o compiledprogram

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

Надеюсь, это помогло вам.

...