Форум программистов, компьютерный форум, киберфорум
Наши страницы
Evg
Войти
Регистрация
Восстановить пароль
Рейтинг: 2.33. Голосов: 3.

Что такое метка на низком уровне

Запись от Evg размещена 20.03.2014 в 14:40
Обновил(-а) Evg 28.03.2014 в 11:14
Метки label, метка

За основу взят данный пост: http://www.cyberforum.ru/asm-beginne...ml#post5938013. Изначально предполагал более развёрнутое объяснение поместить там, но в очередной раз получилось много букв и решил выделить в блог, т.к. скорее всего что-то придётся исправлять, к тому же информация может оказаться полезной и для других. В этом смысле данную статью нельзя рассматривать как законченную статью. Скорее это просто рабочая страница из записной книжки с мыслями "чтобы не потерялось"

Чтобы не вводить в заблуждение, сразу же оговариваю две вещи. Я НЕ считаю ассемблер языком программирования (прошу не путать понятия "язык" и "язык программирования" - это НЕ одно и то же). Под словом "метка" подразумевается НЕ метка языка программирования, а метка в понимании ассемблерного программирования. Все попытки начать спор на тему того, является ассемблер языком программирования или нет, буду удалять. Хотелось бы переносить все такие посты куда-нибудь в другое место, но технически такой возможности нет (во всяком случае у меня). Если кому-то очень хочется поспорить - на форуме есть специальный раздел для холиваров




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

Следующий пример я компилировал на i386-linux, а потому синтаксис ассемблерного файла соответствующий. При перекладывании кода под другие системы ассемблерный файл нужно будет переписать под свой синтаксис. Возможно, что ещё и имя метки надо будет поменять, т.к., например, gcc от cygwin'а ко всем глобальным именам пририсовывает символ подчерка

C
/* Файл t.c */
#include <stdio.h>
 
/* Конкретный тип нам не важен, т.к. мы здесь работаем только с адресом
 * внешней метки с именем "label" */
extern int label;
 
int main (void)
{
  printf ("&label = %p\n", &label);
  return 0;
}
Assembler
# Файл t1.s
  .globl label
label:
  .byte 0
Что у нас написано в исходниках? В программе на Си мы описали внешнюю переменную с именем label и хотим напечатать её адрес. С точки зрения языка программирования мы работаем с переменными, в то время как с точки зрения ассемблера или бинарного файла у нас нет никаких переменных, а есть только данные в памяти и метки. В файле t1.s мы создаём 1 байт данных, куда помещаем байт с нулевым значением и создаём метку label, которая настроена на этот байт. По умолчанию ассемблер считает, что всё создаётся в исполняемых секциях, а потому этот нулевой байт создан именно в секции с кодом, а не в секции с данными. Это не играет никакой принципиальной роли, т.к. мы просто хотим напечтать адрес метки (а более правильно - "значение метки")

Код:
$ gcc t.c t1.s
$ ./a.out 
&label = 0x804840c
Таким образом, после линковки у нас в коде программы оказался нулевой байт. Адрес этого нулевого байта записан в метку label. Значение метки label равно 0x804840c. Другими словами, в память по адресу 0x804840c и попал наш нулевой байт

Пока тут было всё более менее стандартно

Теперь возьмём другой ассемблерный файл

Assembler
# Файл t2.s
  .globl label
  .set label, 0x11223344
Что тут делается? Заводится метка с именем label и значением 0x11223344. При этом не заводятся никакие данные. Т.е. метка висит абстрактно, не указывает ни на какие конкретно данные, но при этом имеет конкретное значение 0x11223344

Код:
$ gcc t.c t2.s
$ ./a.out 
&label = 0x11223344
Да, для многих это будет непривычно, но именно так устроен мир. Метка - это некая сущность, которая имеет некоторое целочисленное значение. В обычной жизни метка ассоциируется с блоком данных или фрагментом кода, а потому значение метки трактуется как адрес переменной, адрес функции или адрес некоторой точки внутри функции. Это бытовое понятие метки. Но на самом деле метка, как уже говорилось - это абстрактная конструкция, описывающая ЗНАЧЕНИЕ, которое может в том числе совпадать с адресом другой сущности

Давайте посмотрим, как в объектном файле в реальности представлена метка:

C
/* Файл t.c */
int a = 1;
int b = 2;
int c = 3;
Код:
 $ gcc t.c -c

$ readelf --sections t.o
...
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
...
  [ 2] .data             PROGBITS        00000000 000034 00000c 00  WA  0   0  4
...

$ readelf --symbols t.o
...
   Num:    Value  Size Type    Bind   Vis      Ndx Name
...
     7: 00000000     4 OBJECT  GLOBAL DEFAULT    2 a
     8: 00000004     4 OBJECT  GLOBAL DEFAULT    2 b
     9: 00000008     4 OBJECT  GLOBAL DEFAULT    2 c
...

$ objdump -s t.o
...
Contents of section .data:
 <0000> 01000000 02000000 03000000
...
В печати "readelf --sections" нам нужно только то, что секция .data (секция с данными) имеет номер 2 (колонки Name и [Nr]). Интерес для нас представляет печать "readelf --symbols". Метки представлены в колонке "Name". У меток есть секция (колонка Ndx), для которой 2 означает секция .data. У меток есть значение (колонка Value). Видим, что, например, метка "c" описана как метка, лежащая в секции .data и имеющая значение 8. Что означает, что метка адресует блок памяти в секции .data со смещением 8 байт от начала секции. Последняя печать "objdump -s" просто показывает побайтное содержимое секции .data. Т.е. у всех потребители метки "c" точка использования "c" будет заменена на адрес секции .data, соответствующий модулю t.o + 8 байт. Итоговый адрес этой конструкции будет знать только линкер

Теперь возьмём другой исходник

Assembler
# Файл t.s
  .globl a
  .set a, 0x11223344
  .globl b
  .set b, 0x55667788
  .globl c
  .set c, 0x99aabbcc
Код:
$ gcc t.s -c

$ readelf --symbols t.o
...
   Num:    Value  Size Type    Bind   Vis      Ndx Name
...
     4: 11223344     0 NOTYPE  GLOBAL DEFAULT  ABS a
     5: 55667788     0 NOTYPE  GLOBAL DEFAULT  ABS b
     6: 99aabbcc     0 NOTYPE  GLOBAL DEFAULT  ABS c
...
Здесь в качестве секции для меток указано ABS. Это псевдосекция, говорящая о том, что метка ассоциирована не с какими-то конкретными данными, а имеет значение, не привязанное к данным (абсолютное значение). Т.е. у всех потребители метки "c" точка использования "c" будет заменена на значение 0x99aabbcc. Подстановкой значения так же будет заниматься линкер, но делаться будет это немного по другим правилам.

Если взять два последних примера и прилинковать их к другим модулям, которые потребляют метки "a", "b" и "c", то для всех прочих потребителей оба варианта получения объектного файла t.o принципиально ничем не будут отличаться. Есть точка потребления метки, есть модуль, в котором описано определение метки. Просто в одном случае определение метки ассоциировано с некоторыми данными (т.е. value метки есть адрес точки внутри данных), а во втором случае определение метки есть просто число
Всего комментариев 18
Комментарии
  1. Старый комментарий
    Аватар для Mikl___
    Evg,
    по поводу того, что в ассемблере "нет таких понятий как переменная и массив"
    Цитата:
    Сообщение от Mikl___
    По этой директиве описывается переменная X
    Цитата:
    Сообщение от Evg
    На самом деле заводится 1 байт данных, в метку X присваивается адрес этого байта
    Цитата:
    Сообщение от Mikl___
    Для описания переменной-массива с некоторыми начальными значениями, применяется директива DB с несколькими операндами
    Цитата:
    Сообщение от Evg
    На самом деле просто заводится блок памяти в 4 байта, эти байты инициализируются значениями 2, -2, <undef>, '*', а в метку M присваивается адрес первого байта. Нету никаких переменных или массивов.
    Студентам обучающимся применению языка ассемблер в качестве практики предлагается писать программы, которые выполняют арифметические, логические, статистические и т.д. вычисления, в которые входят переменные и массивы (одно/двух/многомерные), и хотя с точки зрения компилятора данные (переменные и массивы) можно рассматривать как метки, но на этом основании, говорить о том, что "Нету никаких переменных или массивов" так же не стоит
    Запись от Mikl___ размещена 22.03.2014 в 09:22 Mikl___ вне форума
    Обновил(-а) Mikl___ 22.03.2014 в 09:31
  2. Старый комментарий
    Аватар для Evg
    Переменная языка является меткой в ассемблере. Обратное неверно. В ассемблере нету понятия переменной или массива. Такие понятие есть только в голове программиста, который пишет программу. Т.е. он ТРАКТУЕТ кусок памяти, как переменную типа int16, массив из 10 элементов типа float32 и т.д.

    Я не знаю, чего там преподают студентам. Скорее всего, расписывать через переменные понятнее и проще. А в качестве результата имеем тех, кто "выучил ассемблер", но по прежнему нифига не разбираются во внутренностях машины или хотя бы в бинарных файлах
    Запись от Evg размещена 22.03.2014 в 11:54 Evg вне форума
  3. Старый комментарий
    Аватар для taras atavin
    Метка языка высокого уровня - это просто адрес некоторого места в коде.
    Запись от taras atavin размещена 22.03.2014 в 12:59 taras atavin вне форума
  4. Старый комментарий
    Аватар для Evg
    Цитата:
    Сообщение от taras atavin Просмотреть комментарий
    Метка языка высокого уровня - это просто адрес некоторого места в коде.
    В начале добавил абзац с уточнением, что есть "метка" в контексте данной статьи
    Запись от Evg размещена 22.03.2014 в 13:21 Evg вне форума
  5. Старый комментарий
    Аватар для programina
    Под меткой всегда подразумевала ту самую метку, на которую переходят инструкцией goto. Но здесь, я полагаю, имеются в виду совсем другие метки. Программа readelf выводит таблицу из объектного файла с этими метками в поле [I]name[/I], то есть эти метки живут в объектных файлах?
    Запись от programina размещена 22.03.2014 в 23:17 programina вне форума
  6. Старый комментарий
    Аватар для Evg
    Да, именно эти. В терминах объектных файлов эти метки называют symbol (в русскоязычной терминологии часто называют "символьная ссылка"). Symbol - это та сущность, через которую работает линковка. Если ты будешь смотреть не объектный файл, а ассемблерный (выдача по -S), то основная масса символов на ассемблере выглядит как имя и двоеточие, что обычно называется "метка"
    Запись от Evg размещена 22.03.2014 в 23:26 Evg вне форума
  7. Старый комментарий
    Аватар для programina
    [SPOILER=q.cpp][CPP=-1]
    int main()
    {
    int a = 7, b = 9;

    if(a < b) a += b;
    }[/CPP][/SPOILER][SPOILER=g++ -S q.cpp][PERL=-1]
    .file "q.cpp"
    .def ___main; .scl 2; .type 32; .endef
    .text
    .globl _main
    .def _main; .scl 2; .type 32; .endef
    _main:
    pushl %ebp
    movl %esp, %ebp
    andl $-16, %esp
    subl $16, %esp
    call ___main
    movl $7, 12(%esp)
    movl $9, 8(%esp)
    movl 12(%esp), %eax
    cmpl 8(%esp), %eax
    jge L2
    movl 8(%esp), %eax
    addl %eax, 12(%esp)
    L2:
    movl $0, %eax
    leave
    ret[/PERL][/SPOILER]
    То есть [I][B]L2:[/B][/I] - это метка, [I][B]_main:[/B][/I] получается тоже метка? Интересно, что будет если в строчке [I][B]jge L2[/B][/I] написать [I][B]jge _main[/B][/I], а затем каким-нибудь образом скомпилировать этот файл с асемблером и запустить полученную программу.
    Запись от programina размещена 23.03.2014 в 01:17 programina вне форума
    Обновил(-а) programina 23.03.2014 в 01:31
  8. Старый комментарий
    Аватар для Evg
    Цитата:
    Сообщение от programina Просмотреть комментарий
    То есть L2: - это метка, _main: получается тоже метка?
    Именно так

    Цитата:
    Сообщение от programina Просмотреть комментарий
    Интересно, что будет если в строчке jge L2 написать jge _main, а затем каким-нибудь образом скомпилировать этот файл с асемблером и запустить полученную программу.
    "Каким-нибудь образом скомпилировать" можно, подав команду "g++ q.s", где q.s - это файл, полученный приказом "g++ -S q.cpp" и нужным образом отредактированный. Если поменять "jge L2" на "jge _main", то конкретно в данном случае ничего не будет, т.к. этот переход всё равно не исполнится (потому что 7 меньше 9), но если сделать a = 10, то при исполнении управление честно передастся на метку _main. А дальше скорее всего зациклится (я не знаю, что делается внутри библиотечной ___main)

    Внутри процессора нету ни переменных, ни процедур. Процессор тупо исполняет последовательность операций. Какую последовательность операций подсунули, такую и исполнит. До тех пор, пока не нарвётся на недопустимую операцию (в случае приложения это обращение по некорректному адресу, переход на несуществующий адрес и т.п.)
    Запись от Evg размещена 23.03.2014 в 01:44 Evg вне форума
  9. Старый комментарий
    Аватар для programina
    [QUOTE=Evg;bt9030]g++ q.s[/QUOTE]
    Cкомпилированная программа вылетает с кодом ошибки c00000fd :)
    Запись от programina размещена 23.03.2014 в 02:59 programina вне форума
  10. Старый комментарий
    Аватар для Mikl___
    Цитата:
    Сообщение от Evg Просмотреть комментарий
    Я не знаю, чего там преподают студентам. Скорее всего, расписывать через переменные понятнее и проще. А в качестве результата имеем тех, кто "выучил ассемблер", но по прежнему нифига не разбираются во внутренностях машины или хотя бы в бинарных файлах
    Мужчин и женщин также можно рассматривать как протоплазму или ДНК-цепочки. Но общаться с протоплазмой будет сложнее . Иногда нужно остановится на "верхнем уровне абстракции"...
    Запись от Mikl___ размещена 23.03.2014 в 04:05 Mikl___ вне форума
  11. Старый комментарий
    Аватар для taras atavin
    [QUOTE=Evg;bt9021]В начале добавил абзац с уточнением, что есть "метка" в контексте данной статьи[/QUOTE]Ассемблерная метка - это тоже адресе, но уже не места именно в коде, а вообще любого места в программе, включая место, в котором с точки зрения программиста находится инициируемая с бинарника переменная.
    Запись от taras atavin размещена 23.03.2014 в 05:34 taras atavin вне форума
  12. Старый комментарий
    Аватар для Evg
    Цитата:
    Сообщение от Mikl___ Просмотреть комментарий
    Иногда нужно остановится на "верхнем уровне абстракции"...
    На верхнем уровне абстракции имеет смысл остановиться, если пишешь учебник. А у тебя ведь не учебник, а справочник. Или ты предполагал переписать его. Что-то уже из головы начало всё вылетать.

    Ну а даже если и учебник. В учебниках всегда есть разделы для продвинутого изучения. Для тех, у кого голова работает, никогда не будет лишним описать истинную природу вещей. Просто мне казалось, что если и есть смысл писать свой учебник, то побудительным мотивом для этого должно быть желание написать не так, как у всех, а правильно и удобно
    Запись от Evg размещена 23.03.2014 в 10:24 Evg вне форума
  13. Старый комментарий
    Аватар для taras atavin
    Справочник по языку должен ограничиваться самим языком, учебник должен учить, что хоть и возможно без разбора низкого уровня, но только если учишь совсем уж новичков и требования к знаниям пока не велики.
    Запись от taras atavin размещена 23.03.2014 в 10:49 taras atavin вне форума
  14. Старый комментарий
    Аватар для Evg
    Цитата:
    Сообщение от taras atavin Просмотреть комментарий
    Ассемблерная метка - это тоже адресе, но уже не места именно в коде
    Бытовое понятие метки именно такое. Но в реальности, метка - это по сути дела целочисленное значение, которое совпадает с адресом. А может и не совпадать ни с чьим с адресом. А может. Метка - это то, что в конечном исполняемом коде будет заменено на число.

    ASM
    # максимальное количество очков
    A equ 0x100
     
    # текущее количество очков
    B db 0x0
     
    # записываем в %eax максимальное количество очков
    mov %eax, A
     
    # записываем в %ebx адрес ячейки с количеством текущих очков
    mov %ebx, B
    На бытовом уровне в %ebx мы записываем целочисленную константу, а в регистр %ebx - адрес переменной. Но с точки зрения машины эти два mov'а ничем не отличаются, т.к. записывают в регистр числа. Для связки ассемблер-линкер "A" и "B" являются просто числами. Просто значение числа A можно по простому получить ещё на этапе ассемблирования, а значения числа B можно получить (уже не по простому) на этапе линковки. Но итог будет одинаковый. То, что в ассемблерной программе было меткой, в итоговом коде будет заменено на число. Ибо метка - это всего лишь удобная форма записи, чтобы не оперировать конкретными числами
    Запись от Evg размещена 23.03.2014 в 11:08 Evg вне форума
  15. Старый комментарий
    Аватар для Evg
    Цитата:
    Сообщение от programina Просмотреть комментарий
    Cкомпилированная программа вылетает с кодом ошибки c00000fd
    Если есть желание - запусти из-под отладчика gdb и пошагово протрассируй, найдёшь, где упало. "Пошагово" - в смысле машинного кода, а не языка программирования. "nexti" - команда отладчика, которая выполняет одну инструкцию машины

    Ты можешь передать управление вообще на какую-нибудь глобальную переменную. Программа нормально скомпилируется. Но упадёт на исполнении, т.к. современные операционные системы имеют исполняемые страницы памяти, и неисполняемые. Всё это можно пролечить mprotect'ом
    Запись от Evg размещена 23.03.2014 в 11:13 Evg вне форума
  16. Старый комментарий
    Аватар для Evg
    Цитата:
    Сообщение от taras atavin Просмотреть комментарий
    Справочник по языку должен ограничиваться самим языком, учебник должен учить, что хоть и возможно без разбора низкого уровня, но только если учишь совсем уж новичков и требования к знаниям пока не велики.
    Я ни в коем случае ничего не навязываю. Просто нарисовался момент, который в "обычных" учебниках редко описывается, а потому есть возможность сделать лучше, чем у других. Наверное, это сильно выходит за рамки учебника для начинающих, но тем не менее...
    Запись от Evg размещена 23.03.2014 в 11:16 Evg вне форума
  17. Старый комментарий
    Аватар для Mikl___
    Цитата:
    Сообщение от Evg Просмотреть комментарий
    На бытовом уровне в %ebx мы записываем целочисленную константу, а в регистр %ebx - адрес переменной. Но с точки зрения машины эти два mov'а ничем не отличаются
    Да ну? Очень даже отличаются
    код команда комментарий
    0xbbd0004000mov ebx,0x0040000d0 в ebx число 0x40000d0
    0x8b1dd0004000mov ebx,[0x004000d0]в ebx содержимое памяти по адресу 0x4000d0
    Запись от Mikl___ размещена 24.03.2014 в 11:26 Mikl___ вне форума
    Обновил(-а) Mikl___ 24.03.2014 в 11:37
  18. Старый комментарий
    Аватар для Evg
    В моём примере квадратных скобок не было. Там даже было написано в комментарии "записываем в %ebx адрес ячейки с количеством текущих очков". Адрес ячейки, а не содержимое. Квадратные скобки - это содержимое
    Запись от Evg размещена 24.03.2014 в 21:44 Evg вне форума
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2019, vBulletin Solutions, Inc.
Рейтинг@Mail.ru