Как стать автором
Обновить

Комментарии 31

Почему-то вспомнилась книга Питера Нортона в которой он разрабатывал то ли файловый коммандер, то ли текстовый редактор на ассемблере. Лет 15 назад это было.
> Низкоуровневое программирование — не мой профиль, поэтому если допустил какие-то неточности, буду рад узнать о них в ЛС

Тогда понятно. Статья очень слабая, я бы сказал, что этот материал есть на первой странице любого учебника по АСМ. В куда более толковом, полном и точном виде.

Сам использовал OllyDbg + FASM с немного доработанным MASM синтаксисом, от AT&T глаза на лоб лезут (не привык).
Вы наверное не совсем поняли, но я имел ввиду перевод специфических понятий, ведь я не являюсь непосредственным автором статьи! А касательно самой статьи, то, на мой субьективный взгляд, она очень даже ничего для начинающих. Поэтому я и взялся за ее перевод!
> она очень даже ничего для начинающих

Я не согласен.

Например, тут делается разбор некоторого кода, который использует локальные переменные. Конечно, сначала необходимо объяснить, как x86 работает с данными, потом рассказать, что такое процедуры, зачем они нужны, и как они вызываются, плавно подведя рассказ к работе с глобальными переменными (и может их подвидом — статичными, которые на самом деле отличаются только областью видимости в коде) и локальными, почему они хранятся именно в стеке, аккуратно показав рекурсию и работу стека при ее использовании, объяснить, как функционирует стек в целом и зачем нужны два регистра-указателя. Это-бы дало понимание.

Далее рассматривается использование переполнения без объяснения его принципов, о регистре флагов ни слова и одно некорректное предложение насчет знаковости (знаковость существует только в компиляторе и уме программиста, компьютер работает с 1 и 0, впрочем есть специальные инструкции, которые дают выполняют арифметические операции с учетом трактовки данных как знаковых).

И еще раз, я так понял GDB — штука консольная? Зачем она новичкам? OlldyDbg или IDAPro ну куда понятнее.
Я думаю, что вы опять таки не совсем поняли, автор пытается добиться бОльшего понимания С за счет приоткрытия уровней абстракции с помощью ассемблера, а не собирается досконально обучить читателя последнему. Ассемблер здесь средство, а не цель.

Что же касается GDB, то этот вопрос уже обсуждался в коментариях к предыдущей статье (http://habrahabr.ru/post/181738/ ). Присоединяйтесь…
И опять я не согласен.

> Цель этого поста состоит в том, чтобы убедить Вас, что для твердого понимания C нужно так же хорошо понимать ассемблерный код, который генерирует Ваш компилятор C.

По отрывочным и не точным данным никакого понятия не будет.

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

Это все-равно, что объяснять, как ездит автомобиль с помощью описания работы бензонасоса. Ну да, качает бензин. Куда? Откуда? Зачем?

Просто мое мнение — что такого рода материал должен охватывать большинство аспектов, пусть и очень поверхностно, но давая полную картину (если мы все еще говорим о статье для людей не посвященных в вопрос). Тут-же жуется только один нюанс, который человеку не знакомому с низкоуровневым программированием ну вообще ничего не скажет.
Относительно «охвата большинства аспектов», здесь частично с Вами согласен, но в защиту автора могу сказать, что определить это самое «большинство» не так уж и просто. У каждого свой уровень знаний. Что то не понятно, лучше отложить на потом и откатится на одну ступень назад. Я вот о регистрах последний раз что-то внятное еще на первом курсе университета слышал. Ассемблер видел там же. Но прочитав статью и прогуглив попутно то, что не совсем понимал или призабыл, нормально разобрался о чем идет речь. Так что вполне уместно считать, что у статьи есть целевая аудитория.
Немного погуглив, можно и статью не читать.
Вроде как и смысл таких статей в том, чтобы собрать в цельную кучу какой-то кусок информации.
У автора там, часом не было предыдущей статьи, объясняющей регистры, стек и конвенцию вызовов?
Чтобы гуглить нада же какая-то отправная точка. Сферически гуглить в вакууме не всегда приведет к должному результату :)

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

А насчет GDB — не соглашусь. Во-первых, консоль — это не сложно, особенно на таких простых примерах. А во-вторых, на консоли удобно показывать сам принцип, не привязываясь к конкретному gui. Поняв, что происходит здесь, не составит труда повторить все в визуальном отладчике, который может быть и оберткой над gdb.

P.S. и опечаток в статье на момент написания комментария тьма.
Если нашли опечатки, пожалуйста, укажите в ЛС. А то мне текст уже приелся. Перечитывать для проверки бесполезно!
Скорее смысловые ошибки
> Скорость доступа к регистрам очень высокая и именно из-за этого в них часто хранятся операнды арифметических и логических операций.

Нет, просто нет опкодов (почти), которые бы например позволяли сложить два числа, которые находятся в памяти.

> Регистр %rbp всегда имеет высшее значение нежели %rsp

Нет, регистр имеет то значение, которое в него поместили (или изменили соответствующими командами).

> Соглашение о вызовах, в архитектуре x86 гласит, что возвращаемые функцией значения хранятся в регистре %eax

Нет, в архитектуре x86 нет соглашения о вызовах, и его не может быть в архитектуре. В архитектуре есть набор команд, которым можно реализовать вызовы процедуры. Причем, возвращение значения в регистре EAX вообще ничем не обусловлено, с таким-же успехом его можно было-бы возвращать в EBX, участке памяти и на на стеке. С другой стороны, момент о типах вызовов и передаче аргументов вообще упущен в статье.

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

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

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

Нет, она сохраняет свое значение потому, что нет инструкции, которая бы меняла данные, которые находятся по данному аддрессу (вне процедуры). Стек аддрессует точно такую же память, и к ней применимы все (почти) правила, которые и применимы к памяти, которая считается секцией данных (в семействе Windows, никогда не разбирая ядро других ОС). Впрочем, говорить об этом в статье без объяснения локальных переменных, вызовов процедур и прочего смысла нет.

И последнее:

> Также Вы можете обратить внимание, что GDB устанавливает значения для наших переменных, и так же как и во всех переменных в GDB, перед их именем стоит префикс $, в то время как префикс % используется в ассемблере от AT&T.

С какого раза можно понять смысл предложения?)

Что ж, очевидно Вы хотите, чтобы я отдувался за автора. Раз уж я запостил эту статью, то сделаю, что в моих силах :)

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

> Нет, просто нет опкодов (почти), которые бы например позволяли сложить два числа, которые находятся в памяти.

Здесь разве попрошу пояснить более детально…

> Регистр %rbp всегда имеет высшее значение нежели %rsp

> Нет, регистр имеет то значение, которое в него поместили (или изменили соответствующими командами).

Очевидно имеется ввиду адрес участка памяти, который там хранится, и если его не менять, то традиционно он выше у первого. Если же значение регистра изменено руками, то глупо ожидать магии :)

> Соглашение о вызовах, в архитектуре x86 гласит, что возвращаемые функцией значения хранятся в регистре %eax

> Нет, в архитектуре x86 нет соглашения о вызовах, и его не может быть в архитектуре. В архитектуре есть набор команд, которым можно реализовать вызовы процедуры. Причем, возвращение значения в регистре EAX вообще ничем не обусловлено, с таким-же успехом его можно было-бы возвращать в EBX, участке памяти и на на стеке. С другой стороны, момент о типах вызовов и передаче аргументов вообще упущен в статье.

Я так понимаю, вот это самое несуществующее соглашение. Возможно стоит исправить на «Соглашение о вызовах для архитектуры x86»…

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

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

Здесь раздел Scope однозначно нам все объясняет…

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

> Нет, она сохраняет свое значение потому, что нет инструкции, которая бы меняла его (вне процедуры). Стек аддрессует точно такую же память, и к ней применимы все (почти) правила, которые и применимы к памяти, которая считается секцией данных (в семействе Windows, никогда не разбирая ядро других ОС). Впрочем, говорить об этом в статье без объяснения локальных переменных, вызовов процедур и прочего смысла нет.

Что Вы подразумеваете под «нет инструкции». Если воздействовать на этот участок памяти руками, то смотрите ответ выше, касающейся регистров.

> Также Вы можете обратить внимание, что GDB устанавливает значения для наших переменных, и так же как и во всех переменных в GDB, перед их именем стоит префикс $, в то время как префикс % используется в ассемблере от AT&T.

> С какого раза можно понять смысл предложения?)

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

Что касается OllyDbg… Я совсем недавно познакомился с GDB, и скажу честно, если бы увидел сразу такого монстра, как OllyDbg, то не стал бы продолжать. Полносту соглашаясь с gaelpa. Начинать нужно с простого и GDB подходит под эту категорию.
Много букв
1.
> нет опкодов (почти), которые бы например позволяли сложить два числа, которые находятся в памяти

В x86-32 для инструкции ADD допустимы следующие операнды: reg,reg / mem,reg / reg,mem / reg,imm / mem,imm / accum,imm. Вариант mem,mem не предусмотрен, т. е. что бы сложить две переменные — хотя бы одну надо загрузить в регистр.

2.
> Очевидно имеется ввиду адрес участка памяти, который там хранится, и если его не менять, то традиционно он выше у первого.

Бла-бла-бла описание вариантов работы с процедурами, бла-бла-бла описание работы со стеком и локальными переменными, бла-бла-бла большинство компиляторов так и поступают. И Вывод: при таком использовании действительно, BP будет иметь большее значение, чем SP. Вот с этим конечно нельзя не согласиться. С другой стороны компилятор X вообще делает все по другому, и уже такое утверждение не справедливо. Просто нельзя так обобщать.

3.
> Я так понимаю, вот это самое несуществующее соглашение. Возможно стоит исправить на «Соглашение о вызовах для архитектуры x86»

По ссылке «This article describes the calling conventions used on the x86 architecture». Тут я действительно придрался к формулировке, но в самой архитектуре как таковой соглашения и нет.

4.
> Здесь раздел Scope однозначно нам все объясняет…

С точки зрения скомпилированного кода между глобальной и статичной переменной не будет никакой разницы, но она будет между статичной и локальной. В теме ведь обсуждается АСМ?

5.
> Что Вы подразумеваете под «нет инструкции». Если воздействовать на этот участок памяти руками, то смотрите ответ выше, касающейся регистров.

В Ъ АСМ нет понятия переменной. Есть понятие данных (памяти) и меток/аддрессов, по которым осуществляется доступ. Далее, стек — точно такая же память, и если посмотреть, как он работает, то просто станет очевидным, и почему перезаписываются локальные переменные, и почему в них может содержаться мусор, и почему не меняются сами собой статичные переменные. И про память, и про секцию инициализированных данных станет все ясно.

6.
> Также Вы можете обратить внимание, что GDB устанавливает значения для наших переменных, и так же как и во всех переменных в GDB, перед их именем стоит префикс $, в то время как префикс % используется в ассемблере от AT&T.

В GDB перед именем переменных идет символ $, а в синтаксисе AT&T — символ %

7.
> Что касается OllyDbg… Я совсем недавно познакомился с GDB, и скажу честно, если бы увидел сразу такого монстра, как OllyDbg, то не стал бы продолжать.

Вот и зря. В ней все просто, слева листинг кода, справа — регистры, внизу память и стек. И все это можно запускать пошагово, и видеть, как после какой инструкции изменилось состояние, вот например описание www.cracklab.ru/faq/OllyDbg
Нужно понемногу сводить число обсуждаемого к минимуму :)
1.
> В x86-32 для инструкции ADD допустимы следующие операнды: reg,reg / mem,reg / reg,mem / reg,imm / mem,imm / accum,imm. Вариант mem,mem не предусмотрен, т. е. что бы сложить две переменные — хотя бы одну надо загрузить в регистр.

Но ведь ничего противоречящего в статье выше сказанному нет. Просто сказано в двух словах без каких-либо детальных пояснений. Да регистры быстрыя… Да часто хранятся операнды операций…

2.
> Бла-бла-бла описание вариантов работы с процедурами, бла-бла-бла описание работы со стеком и локальными переменными, бла-бла-бла большинство компиляторов так и поступают. И Вывод: при таком использовании действительно, BP будет иметь большее значение, чем SP. Вот с этим конечно нельзя не согласиться. С другой стороны компилятор X вообще делает все по другому, и уже такое утверждение не справедливо. Просто нельзя так обобщать.

Тот же случай, что и выше. Ну нельзя же все вот так взять и раздробить до мелочей, рассмотрев все возможные детали. На таком этапе читателю достаточно знать, что большинство компиляторов так поступают…

3.
> По ссылке «This article describes the calling conventions used on the x86 architecture». Тут я действительно придрался к формулировке, но в самой архитектуре как таковой соглашения и нет.

Вас понял. Поправил формулировку!

4. и 5.
> В Ъ АСМ нет понятия переменной. Есть понятие данных (памяти) и меток/аддрессов, по которым осуществляется доступ. Далее, стек — точно такая же память, и если посмотреть, как он работает, то просто станет очевидным, и почему перезаписываются локальные переменные, и почему в них может содержаться мусор, и почему не меняются сами собой статичные переменные. И про память, и про секцию инициализированных данных станет все ясно.

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

6.
> В GDB перед именем переменных идет символ $, а в синтаксисе AT&T — символ %

Поправил на что-то среднее :)

7.
> Вот и зря. В ней все просто, слева листинг кода, справа — регистры, внизу память и стек. И все это можно запускать пошагово, и видеть, как после какой инструкции изменилось состояние, вот например описание www.cracklab.ru/faq/OllyDbg

С точки зрения профессионального использования — Вы безусловно правы. Но чтобы ознакомиться на маленькой программке лучше все же GDB. Ввел простенькую команду — получил наглядный результат — осознал происходящее.
Уже ближе
1. > Но ведь ничего противоречящего в статье выше сказанному нет.

Ну нет же. В статье говорится — используем регистры, потому что так быстрее. На самом деле по другому просто нельзя.

2. > На таком этапе читателю достаточно знать, что большинство компиляторов так поступают…

Вот именно, так поступает большинство компиляторов. А не «так было и будет всегда».

4. и 5. > а разбираемся мы в С, поэтому и оперируем понятиями С.

Мое мнение — что если хочется понять логику работы — то таки придется разбираться в АСМ, благо это не так сложно.

7. > Но чтобы ознакомиться на маленькой программке лучше все же GDB. Ввел простенькую команду — получил наглядный результат — осознал происходящее.

Я в свое время учился на Olly, и в действии она намного проще, чем кажется.

Возможно, даже совсем близко... :)
1.
> Ну нет же. В статье говорится — используем регистры, потому что так быстрее. На самом деле по другому просто нельзя.

Ну здесь похоже дело в моей формулировке. Более точнее перевод может звучать как: «Скорость доступа к регистрам очень высокая и в них часто хранятся операнды арифметических и логических операций», что вроде и не противоречит тому, что говорите Вы (хотя может что и противоречит)…

2.
> Вот именно, так поступает большинство компиляторов. А не «так было и будет всегда».

Ну это начинающему еще предстоит узнать, в контексте пунктов 4 и 5, с коими я полностю соглашусь. Разбиратся придется и чем детальнее, тем лучше. Но не все сразу. Вам, как профессионалу, эта статья просто таки пестрит оплошностями, а вот мне, например, с не таким высоким уровнем низкоуровневого програмирования она была достаточно информативна. Очевидно, что чем дальше в лес… Ну Вы знаете…

7.

Мне кажется, здесь своеобразный порог и для каждого свой. Но азы есть азы… И все равно начавший с GDB человек рано или поздно будет искать инструмент с бОльшими возможностями. Тот же Olly… Вы уже начали с него, что плюс для Вас же… И, к стати, как я вижу он только для Windows?
Почти...
1.
Правильно сказать так: В большинстве инструкций, в т. ч. использующих 2 операнда хотя бы один из них должен являться регистром. В других случаях операнд может быть памятью или указан непосредственно в инструкции, но вариант с регистром всегда исполняется заметно быстрее.

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

> Вам, как профессионалу

К слову, я вообще не профессионал, и моя деятельность не имеет к ИТ никакого отношения. Впрочем, в юношестве, я разрабатывал один проект, и, из-за юношеского-же максимализма делал его на АСМ. Это было адово, когда файл разросся до 10к строк кода. Я использовал только переходы (аналог на C — go to), и попробуйте представить, какой там был спагетти код. И конечно-же (кто-бы мог подумать?) поддерживать программу стало сложнее. Нестройные ряды пользователей до сих пор поминают меня незлым тихим словом и лелеют надежду, что я выпушу исправления…

7. Да, OllyDbg — только под windows, как уже и написали ниже. Но она действительно крутая.

Все, что смог...
1.
> Правильно сказать так: В большинстве инструкций, в т. ч. использующих 2 операнда хотя бы один из них должен являться регистром. В других случаях операнд может быть памятью или указан непосредственно в инструкции, но вариант с регистром всегда исполняется заметно быстрее.

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

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

2.
> К слову, я вообще не профессионал, и моя деятельность не имеет к ИТ никакого отношения. Впрочем, в юношестве, я разрабатывал один проект, и, из-за юношеского-же максимализма делал его на АСМ. Это было адово, когда файл разросся до 10к строк кода. Я использовал только переходы (аналог на C — go to), и попробуйте представить, какой там был спагетти код. И конечно-же (кто-бы мог подумать?) поддерживать программу стало сложнее. Нестройные ряды пользователей до сих пор поминают меня незлым тихим словом и лелеют надежду, что я выпушу исправления…

Все еще сильны в этой области, а значит в какой то мере профессионал. Да и такой бесценный опыт за плечами!

7.
> Да, OllyDbg — только под windows, как уже и написали ниже. Но она действительно крутая.

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

Что ж, пытался оправдать автора (да и себя), как мог! :)
Просто пару скриншотов OllyDbg, что-бы таки было понятно, что с чем сравнивают. Тут тебе и стек, и весь листинг, и регистры, и память, и точки остановки, и еще куча всего, что способствует.
И существует он, к сожалению, только под windows в отличие от gdb. Жаль что схожие программы мне не попадались под os x/linux.
Не надо хоронить gdb просто из-за того что он консольный и из коробки не очень красив на первый взгляд. Но с gdb -tui уже выглядит получше(есть лэйауты с листингом, регистрами и тп), и для тех кто не живет в дебагере каждый день и вообще дизассемблерный код смотрит раз в месяц и реже — вполне уже пригоден. Может пригоден и для тех кто активно дебагит и смотрит ассемблерный листинг каждый день, но так как я к таким не отношусь — судить не возьмусь. Плюс при должной фантазии gdb можно использовать для самых разных целей, как пример: poormansprofiler.org/.

P.S. Осуждать инструмент которым сами и не пытались пользоваться, это как-то не очень хорошо наверное…
Когда то разобраться с виртуальной памятью и регистрами очень помог bitfry.narod.ru/. Да и вообще изменил мой взгляд на программирование.
Это может показаться странным, но я бы такой эксперимент порекомендовал проводить на простейшем микроконтроллере. Си очень простой язык. Архитектура контроллера тоже простая. Ассемблер RISC проца тоже легкий. Вот где надо учить Си таким способом. Не на x86 точно.
А я не совсем понял причем здесь Си? Точнее почему именно Си? Тоже самое произойдет и при использовании любого другого языка, все зависит от компилятора. Т.е. хочу сказать, что Си после этой статьи («Разбираемся в С, изучая ассемблер») лучше понимать не стал. Зато больше разобрался в ассемблере.
Принято считать, что си «прозрачнее» чтоли транслируется в ассемблер по сравнению с другими общепринятыми компилируемыми языками.
Это понятно. Просто из названия статьи следует, что будут пояснения к Си через ассемблер, но здесь просто взята программа с кодом на Си. И никакого пояснения конкретно по Си.
Было бы правильнее назвать статью: «Изучаем ассемблер, разбирая С». Но это уже вопрос к автору, а не переводчику.
Переводчику большое спасибо за статью, весь этот материал я знал еще в вузе, но такого подхода к обучению С и вообще пониманию ни разу не встречал, на мой взгляд это лучший подход. Такой подход заставляет человека думать, есть куча других в которых человеку подсовывают графический отладчик, который сильно облегчает ментальный труд, и приучает человека не думать, а видеть как выполняется его программа.

Решил сегодня чуть-чуть вникнуть в Асм через C. Через генерацию gсс c флагом -S побегал по строчкам Асма. Оказывается, можно в C простым способом через вставки asm менять значения переменных в регистрах напрямую. Что-то вроде моего hello world на асме:

#include <stdio.h>

int main () {
int a = 1;
/Печатаем до кода на Ассемблере/
printf("%d\n",a);
asm (
"movl $7, -4(%rbp);" /Кладём число 7 в стек по смещением -4./
);
printf("%d\n",a);
}

Статья нашлась по ключевому слову rbp. Спасибо.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории