
TextmetukSPO
.pdfЭтот вариант появится, если было произведено переключение на ассемблер типа intel.
По умолчанию стандартный режим отладчика gdb не отображает информацию о текущей команде в пошаговом режиме этого отладчика. (Это вполне целесообразно, так как он обычно используется для отладки программ, написанных на языке высокого уровня.) Для установки режима автоматического отображения следующей команды следует передать отладчику приказ в виде директивы
display/i $eip
после чего в нашем примере автоматически выдается (с учетом предыдущей остановки на первой точки приостановки)
1: x/i $eip 0x8048085 <beg> mov %esi, 0xa
(если текущим режимом отображения команд будет режим intel).
Само приказание на выполнение одной следующий команды задается в виде команды отладчика
stepi
В частности, в нашем примере после полученной выше остановки на первой точке приостановки, выдача команды stepi приводит к следующей реакции отладчика в виде
(gdb) stepi 0x804808a in beg()
1: x/i $eip 0x804808a <beg+5> mov %ecx, 0x0
Дальнейшие выдачи команды stepi (в частности, после извлечения ее в командную строку с помощью клавиши "стрелка вверх") будут давать следующие выво-
ды |
|
|
(gdb) stepi |
|
|
0x804808f in pov() |
|
|
1: x/i $eip 0x804808f <pov>: |
mov |
%edx,0x0 |
(gdb) stepi |
|
|
0x804808a in pov() |
|
|
1: x/i $eip 0x8048094 <pov+5>: |
div |
%esi, %eax |
(gdb) stepi |
|
|
0x804808a in pov() |
|
|
1: x/i $eip 0x8048096 <pov+7>: |
add |
$0x30, %dl |
Для отображения информации служит команда с именем, восходящим к слову print, но задаваемая обычно единственным начальным символом p. В частности, выдача содержимого регистра в виде десятичного числа требует задания команды в виде p $имя_регистра, для вывода в виде шестнадцатеричного числа - команда задается в виде p/x $имя_регистра. Причем имя регистра в этой команде допускается только для 32-битных регистров (регистры меньшего размера все равно являются частью некоторого 32-битного регистра).
При отладки рассматриваемой программы-примера в качестве первой цифры должен появиться символ '6', и, действительно, в ситуации остановки перед строкой <pov+7>, т.е. после вывода последней из изображенных выше строк информации от отладчика, запрос
p $edx
приводит к выдачи информации в виде
21
$1 = 0x6
а запрос в виде p/x $eax
к выводу результата $2 = 0x199
Аналогичный запрос в этом же месте отлаживаемой программы в виде p/x $eax
дает результат $3 = 409
В самом конце прохождения программы с помощью отладчика, когда будет достигнут ее конец, на экран выдается сообщение
Program exited with code число
где параметр число есть значение, переданное в регистре ebx при вызове первой функции (функции exit) Linux..
Следует иметь в виду, что программисту предоставляется возможность установки любого нужного ему множества контрольных точек. Для этого проще всего предварительно позаботиться о расстановке (в исходном тексте программы на ассемблере) меток в соответствующих местах программы и при работе отладчика задавать команды в виде
br имя_метки
Не нужные уже метки можно удалить командой clear в виде clear имя_метки
Можно временно выключать (выводить из использования) точки приостановки с помощью команд
disable номер_точки_приостанова
а повторно (или в очередной раз) включить ее в использование с помощью команды
enable номер_точки_приостанова
Команда print (в сокращенной форме задаваемая одной буквой p) может использовать кроме формата, задаваемого символом x (вывод в шестнадцатеричной системе), еще и другие форматы типа. Они задаются символами d, u, o, t и a. Формат d выводит значение как целое со знаком в десятичной системе, формат u задает целое как беззнаковое десятичное, формат o выводит целое в восьмеричной системе счисления, а формат t - в двоичной системе. Для указания вывода в виде отдельного символа служит формат c.
Собственно аргументом команды print могут быть не только регистры, но и имена областей данных, а также адреса, задаваемые в виде шестнадцатеричных констант.
Кроме того, имеется еще более мощная возможность отображения информации, но применимая только к памяти (но не к регистрам). Это команда examine, которая обычно задается единственным начальным символом сокращенного имени - символом x.
Общая форма этой команды имеет вид x/NFU ADDR
где параметр ADDR обозначает адрес места в памяти, параметр N задает число отображаемых элементов, параметр F заказывает формат вывода, а параметр U
22
дает размер информационной единицы. Любой из параметров N, F, U может отсутствовать, тогда берется значение, ранее использованное перед этим или значение по умолчанию (если команда еще не использовалась).
Формат задается одним символом, который либо совпадает с символами формата для команды print, либо дает один из дополнительных форматов s или i. Формат i задает вывод в виде машинных команд, изображаемых ассемблерными мнемокодами, а формат s соответствует ASCIIZ, т.е. символьной строке, заканчивающейся нулевым кодом.
В качестве символов размера информационного элемента допустимо использовать символы b, h и w. Символ b обозначает байты, h обозначает полуслова (два байта), а w - слова (четыре байта) - это и есть начальное значение по умолчанию.
Для продолжения автоматического выполнения команд с текущей точки приостановки (и, обычно, до следующей встречающейся точки приостановки) служит команда отладчика continue, задаваемая обычно единственным символом c.
Для завершения работы отладчика служит команда quit, которую можно задавать единственным символом q.
Универсальный символический отладчик gdb имеет еще множество возможностей, как для отладки высокоуровневых программ, так и пригодных для ассемблерных программ. В частности, он включает средства, позволяющие отлаживать многопоточные приложения и параллельные процессы.
Задание. В программе описывается массив из 30 целых чисел с заданными программистом значениями (при демонстрации программы преподавателю эти числа могут быть изменены последним). Программа запрашивает и вводит число, которое далее служит в качестве верхней границы использования индекса для действий с элементами массива. (Индекс в данном контексте понимается в математическом смысле как порядковый номер элемента.) Оно должно быть не более 30 и программе следует выполнять проверку на допустимость введенного значения.
Программа должна находить минимальное число в начальной части массива, используя только те его элементы, индекс которых в массив ограничен введенным числом, и номер места (индекс) минимального числа в использованной части массива. Результаты в виде числовых значений с поясняющей информацией вывести на экран.
Контрольные вопросы.
1.Каким минимальным может быть значение индексного регистра при доступе к массиву.
2.Какое минимальное число может быть записано в массив, элементы которого определены директивой DD?
3.При наличии в массиве, который описан в программе лабораторной работы, более одного элемента с минимальным значением программа, разработанная вами, будет выдавать индекс места либо первого из них, либо последнего. Как изменить программу, чтобы имел место вариант, отличный от реализованного.
23
Лабораторная работа №5
Содержание работы. Изучение методов и средств использования библиотек объектных модулей в ОС Linux.
Предварительные сведения. При многомодульной разработке программ части этих программ разбиваются на раздельно транслируемые модули. Когда таких модулей становится много, более эффективных техническим решением оказывается их хранение в специальных совокупностях, называемых библиотеками или архивами объектных файлов. Все современные средства компоновки программ позволяют в качестве исходной для них информации применять не только обычные объектные модули, но и библиотеки объектных модулей. Основное преимущество использования таких библиотек заключается в том, что они могут содержать значительно больше модулей, чем необходимо для компоновки конкретной программы, но "подключение" только нужных из них выполняет компоновщик. При этом на программиста не возлагается указание перечня или списка требуемых модулей, вся работа выполняется автоматически.
Поэтому всякая разработка более или менее сложных программ требует либо применения библиотек объектных модулей, либо еще более сложных совокупностей, называемых библиотеками динамической компоновки, которые будут изучаться в последних лабораторных работах этой дисциплины.
При наличии уже готовых библиотек объектных модулей их использование на этапе компоновки требует лишь указания этих библиотек при вызове компоновщика, что производится в соответствии с синтаксисом командного вызова такого компоновщика. В операционных системах Unix и Linux традиционно принято строить вызов компоновщиков таким образом, чтобы библиотеки объектных файлов указывались просто в перечне файлов, на основе которых строится исполняемый файл. В других операционных системах, задание этих библиотек может быть позиционным, когда они должны указываться после какой-то запятой, разделяющей текстовые части командной строки. Кроме того, в зависимости от конкретного разработчика компоновщика могут использоваться ключевые параметры задания библиотек. Эти возможности будут использоваться в следующей лабораторной работе.
Синтаксис динамического компоновщика для современных систем Linux требует указания библиотеки объектных модулей, которые в этих системах традиционно имеют расширение, задаваемое единственной латинской буквой 'a', в общем перечне файлов. Таким образом, если предполагается построение исполняемого файла из объектного файла prog.o и библиотечного файла mylib.a, то в простейшем случае требуется вызывать компоновщик командой вида
ld prog.o mylib.a
Если требуется использовать больше объектных файлов или библиотек, то все они должны быть перечислены в командной строке подобным же образом.
Для практического использования библиотек объектных модулей требуется не только возможность их подключения компоновщиком, но и средства создания таких библиотек, их модификации и пополнения. В Unix подобных системах такими работами занимается служебная программа – утилита с именем ar. Наименова-
24
ние этой утилиты восходит к аббревиатуре для английского слова, называющего действие архивации. Причиной последнего послужило использование именно архивации с сжатием и эффективным хранением первоначально именно для библиотек объектных файлов, когда обработка объемных текстов и их хранение в компьютерных системах еще не была ни только основной, но даже и характерной задачей для технической информатики.
В наиболее употребительной форме утилита ar вызывается в виде ar опции модификаторы имя_библиотеки объектные_файлы
где компонент объектные_файлы представляет собой перечисление имен файлов, разделяемых пробелами.
Основными опциями данной утилиты, которые используются чаще всего, являются опции -r, -d, -m, -t. Первая из них задает добавление нового объектного файла или файлов к библиотеке, вторая опция задает удаление указанных в перечне файлов из библиотеки, третья опция задает перемещение указанных в перечне файлов из библиотеки в текущий каталог, а опция -t приказывает выдать на стандартный вывод перечень файлов, содержащихся в библиотеке. Все перечисленные значения опций не могут комбинироваться друг с другом, а должны использоваться исключающим другие образом. Модификаторы призваны повысить эффективность использования и построения библиотек, и записываются непосредственно за опцией. Модификатор, задаваемый буквой 'c', предназначен для указания, что библиотеку нужно создать.
При многомодульной разработке программ, модули, которые используют данные, описанные в других модулях, на языке ассемблера должны содержать явные указания на такое описание в "другом месте". Для этих целей служат директивы ассемблера, обозначаемые в NASM мнемоникой EXTERN. В качестве операндом такой директивы используют одно или более имен программных объектов, разделяемых друг от друга запятой. Программные объекты, указываемые таким образом, могут быть либо именами областей данных, либо метками сегмента машинных команд. Та или иная разновидность этих имен сообщается программистом транслятору путем помещения директивы EXTERN в соответствующий сегмент исходного текста программы. Указание имен в директивах EXTERN в терминах профессионального программирования называют объявлением имен, в отличие от определения имен, имеющего место в тех частях программы, где директивами DB, DW, DD, RESB, RESW или RESD для областей памяти, обозначаемыми этими именами "выделяется память".
"Обратной стороной" такого указания внешних имен оказывается необходимость задания уточняющей информации для того модуля, где определены эти имена. Эти уточнения задаются в директивах с мнемоникой GLOBAL. Они указывают, какие имена должны быть "видны извне" объектного модуля, иначе говоря, какие имена должны позволять обозначать программные объекты внутри объектного модуля. Такая необходимость связана с тем, что все имена исходной ассемблерной программы, для которых не сделана подобные указания, в объектный файл не попадают, преобразуются в числовые смещения относительно начала соответствующего сегмента. Кроме того, информация о вычисленных таким образом смещениях помещаются в специальные области объектных модулей – разделы задания "перемещаемой информации", которая необходима компоновщику
25
для окончательной настройки машинных кодов с учетом соединения машинных кодов многим объектных модулей и их взаимного размещения друг относительно друг друга. В одной директиве GLOBAL можно указать несколько имен, разделяя их символом запятой.
Задание. Построить библиотеку объектных модулей Linux, содержащих доступные извне процедуры с именами atoi, itoa, lfwrit. Процедура atoi должна выполнять преобразование последовательности цифр, заданной адресом, который содержится регистре eax, в двоичный код, выдаваемой ее также через регистр eax. Процедура itoa должна выводить в стандартный вывод последовательность десятичных цифр, представляющих исходное значение аргумента в регистре eax, заданное им в двоичном коде. Процедура lfwrite переводит экранный вывод на следующую строку. Основная часть программы должна содержать массив записей вида {число, текст} и по введенному числу выводить текст из той записи, значение первого поля которой совпадает с этим числом. Процедуру поиска такой записи на завершающей стадии разработки целесообразно также поместить в библиотеку объектных модулей. Основная часть программы должна использовать библиотеку объектных модулей.
Контрольные вопросы.
1.В каком месте файловой системы ищутся библиотеки объектных модулей, указанные внутри строки вызова компоновщика только собственным именем. Найти ответ в справочной системе на компоновщик.
2.Как указать нестандартное место размещения библиотек объектных моду-
лей?
3.Что получается с содержимым библиотеки объектных модулей, если в нее заноситься новая версия объектного файла, сохраняется ли в ней старая версия?
Лабораторная работа №6
Содержание работы. Изучение методов и средств использования библиотек объектных модулей в ОС Windows.
Предварительные сведения.
В связи со сложившейся спецификой операционных систем типа Windows, ориентированных в первую очередь на неквалифицированных пользователей и, поэтому, использующих преимущественно графический интерфейс, преобладающая часть отладчиков для этих ОС обеспечивают отладку только графических приложений. В частности только этими возможностями обладают отладчики известной фирмы Borland/Inprise.
К настоящему времени отдельные самостоятельные разработчики предлагают неплохие программные продукты - не очень известные среди корпоративных пользователей, но эффективные, надежные и, главное для высшей школы, - не требующие материальных затрат на их приобретение. В качестве такого программного инструмента рассмотрим отладчик OllyDbg, разработанный Oleh
26
Yuschuk и доступный по электронному адресу на Internet сайте http://home.t- online.de/home/Ollydbg.
Отладчик OllyDbg, версию 1.09 которого мы рассматриваем, есть 32-битный отладчик ассемблерного уровня с внутренними средствами анализа кода для Microsoft Windows(R). Он не требует инсталляции и для использования его программные компоненты нужно просто поместить в отдельное оглавление. Данный отладчик представляет собой свободно распространяемое программное обеспечение (shareware) с авторскими правами - Copyright (C) 2000-2003 Oleh Yuschuk.
Имя исполняемого файла отладчика - OLLYDBG.EXE, по которому его и следует запускать. Имя исполняемого файла отлаживаемой программы может задаваться как аргумент строки вызова отладчика. Отладчик имеет очень широкие возможности, в том числе отладку графических приложений и библиотек динамической компоновки. Поэтому его пользовательский интерфейс основан на графическом окне приложения (а не текстовом окне, как ранее рассмотренный отладчики gdb для Linux).
Практически более удобно запускать отладчик из его собственного оглавления, как текущего при таком запуске (или с помощью ярлыка). Причем, запускать без аргумента - имени исполняемого файла, а исполняемый файл задавать с помощью меню отладчика. Именно, после начального запуска программы OLLYDBG.EXE следует использовать раздел File в главном меню отладчика и пункт Open этого раздела для задания через диалоговую панель имени исполняемого файла - в любом желаемом оглавлении.
Основная информация, предоставляемая разработчику отладчиком, размещается в нескольких панелях основного окна отладчика. Основной панелью является панель, называемая окно CPU, которая предоставляет дизассемблированный текст исполняемого файла.
Работа отладчика OllyDbg управляется с помощью мыши и (или) с помощью клавиатуры, в частности управляющих комбинация "горячих" клавиш.
Точки приостановки (контрольные точки) устанавливаются выбором требуемой строки дизассемблированного текста в окне CPU и нажатием клавиши F2, причем эта клавиша действует как переключатель установки контрольной точки - повторное нажатие этой клавиши на строке с уже установленной контрольной точкой выключает ее (контрольная точка на данной строке изчезает).
Для удобства демонстрации конкретных средств отладки в качестве опорного примера возьмем программу, приведенную в листинге 3.
; Вывод десятичного значения содержимого EAX в MS-DOS EXTERN GetStdHandle, WriteFile, ExitProcess
SECTION CODE USE32 CLASS=CODE
..start:
push dword STD_OUTPUT_HANDLE call GetStdHandle
mov [hstdout],eax mov eax, 1000h
mov esi,10 ; base of position digit system mov ecx, 0 ; reset digit counter
pov: mov edx, 0 ; null into left part of devident
27
div esi |
; divide for next digit = rest |
add dl, '0' |
|
push dx |
|
inc ecx |
; step into counter |
cmp eax, 0 |
|
jne pov
mov [cnt], ecx mov ebx, numtxt
izv: pop dx
mov byte [ebx],dl ; digit into array for text value inc ebx
loop izv ; izv,ecx
;--- WriteFile(hstdout, txt, 7, &actlen, NULL) push dword 0
push dword actlen
push dword [cnt] ; number of bytes push dword numtxt ; address of txt push dword [hstdout] ; N handle=hstdout call WriteFile
; ExitProcess(0) push dword 0 call ExitProcess
SECTION DATA USE32 CLASS=DATA
numtxt times 10 db 0 ; may be <resb 10> but then - warning! cnt dd 0
hstdout dd 0 actlen dd 0
STD_OUTPUT_HANDLE equ -11
Листинг 3. Пример программы PRIM4.asm для отладки
После запуска под отладчиком исполняемого файла, полученного ассемблированием и компоновкой программы с листинга 2, в панели CPU отладчика можно наблюдать следующий текст, приведенный на рис. 1.
00401000 |
68 F5FFFFFF PUSH -0B |
; |
DevType |
= |
|
STD_OUTPUT_HANDLE |
|
|
|
||
00401005 |
E8 64000000 |
CALL <JMP.&KERNEL32.GetStdHandle> |
; GetStdHandle |
|
|
0040100A |
A3 0E204000 |
MOV DWORD PTR DS:[40200E],EAX |
|
|
|
0040100F |
B8 00100000 |
MOV EAX,1000 |
|
|
|
00401014 |
BE 0A000000 |
MOV ESI,0A |
|
|
|
00401019 |
B9 00000000 |
MOV ECX,0 |
|
|
|
0040101E |
BA 00000000 |
MOV EDX,0 |
|
|
|
00401023 |
F7F6 |
DIV ESI |
|
|
|
00401025 |
80C2 30 |
ADD DL,30 |
|
|
|
00401028 |
66:52 |
PUSH DX |
|
|
|
0040102A |
41 |
INC ECX |
|
|
|
0040102B |
3D 00000000 |
CMP EAX,0 |
|
|
|
28
00401030 |
75 EC |
JNZ SHORT PRIM4.0040101E |
|
|
|
00401032 |
890D 0A204000 MOV DWORD PTR DS:[40200A],ECX |
|
|||
00401038 |
BB 00204000 |
MOV EBX,PRIM4.00402000 |
|
|
|
0040103D 66:5A |
POP DX |
|
|
|
|
0040103F |
8813 |
MOV BYTE PTR DS:[EBX],DL |
|
|
|
00401041 |
43 |
INC EBX |
|
|
|
00401042 |
E2 F9 |
LOOPD SHORT PRIM4.0040103D |
|
|
|
00401044 |
68 00000000 |
PUSH 0 |
|
; pOverlapped = NULL |
|
00401049 |
68 12204000 |
PUSH PRIM4.00402012 ; pBytesWritten = PRIM4.00402012 |
|||
0040104E |
FF35 0A204000 PUSH DWORD PTR DS:[40200A] |
|
; nBytesToWrite = |
||
0 |
|
|
|
|
|
00401054 |
68 00204000 |
PUSH PRIM4.00402000 |
; Buffer = PRIM4.00402000 |
||
00401059 |
FF35 0E204000 PUSH DWORD PTR DS:[40200E] |
|
; hFile = NULL |
||
0040105F |
E8 10000000 |
CALL <JMP.&KERNEL32.WriteFile> |
; WriteFile |
|
|
00401064 |
68 00000000 |
PUSH 0 |
|
; ExitCode = 0 |
|
00401069 |
E8 0C000000 |
CALL <JMP.&KERNEL32.ExitProcess> |
; ExitProcess |
||
0040106E |
$-FF25 38304000 JMP DWORD PTR DS:[<&KERNEL32.GetStdHandl>] |
||||
00401074 |
|
$-FF25 3C304000 JMP DWORD PTR DS:[<&KERNEL32.WriteFile>] |
|||
0040107A |
.-FF25 40304000 JMP DWORD PTR DS:[<&KERNEL32.ExitProcess>] |
||||
00401080 |
|
00 |
DB 00 |
|
|
Рис. 1. Содержимое окна CPU отладчика для примера
Заметим, что окно дизассемблирования CPU состоит из четырех столбцов. В первом из них для каждой строки приводится (виртуальный) адрес, с которого размещается содержимое этой строки при выполнении программы. Во втором столбце приводится шестнадцатеричный код содержимого этой строки, в третьем - дизассемблированное содержимое, записанное с помощью мнемокодов команд, обозначений регистров и вспомогательных операндов ассемблера. Кроме того, в этой же строке для операндов, задающих места переходов или вызываемых подпрограмм, в угловых скобках приведены обозначения соответствующих исполняемых файлов и имена доступных извне меток или подпрограмм в этих исполняемых файлах. В частности, в нашем примере для обращения к функциям API, находящимся в DLL библиотеки KERNEL32, используются программные переходники, которые представляют собой обращения по командам CALL к ко- мандам-переходникам в конце исполняемого кода. Эти команды-переходники являются, в свою очередь, косвенными безусловными переходами, которые осуществляют переходы по адресам, записанным в специальные поля и обеспечивающие действительные переходы на требуемые системные функции. (Тем самым, в реализацию пожеланий исходной программы система разработки вносит свои изменения - вместо прямых обращений к системным функциям используются упомянутые переходники.)
Последняя колонка панели CPU содержит пояснения, помогающие разработчику понять, что делает команда в рассматриваемой строке. Эти пояснения могут быть системным обозначением константы, используемой в команде строки, либо комментариями к используемым действиям и вызовам.
Для выполнения программы в пошаговом режиме служат клавиши F7 и F8. Первая из них задает пошаговое исполнение с пошаговым вхождением и выполнением вызываемых подпрограмм. Клавиша F8 задает пошаговый режим только
29
для текущей программы (подпрограммы), при этом выполнение всех вызываемых подпрограмм осуществляется автоматически до момента выхода из них, так что отлаживаемые действия приостанавливаются на следующей за вызовом команде. Автоматическое выполнение программы задается клавишей F9, при этом выполнение происходит до первой встретившейся контрольной точки, до системного вызова, следствием которого является ожидание события (например, ввод данных) или завершения программы. Повторный запуск программы осуществляется горячей клавишей Ctrl-F2.
Текущее значение данных в рассматриваемый момент отладки программы можно получить из двух панелей отладчика: окна Register и окна Dump. Первое из них отображает текущие значения всех регистров, а окно предназначено для отображения участка памяти. Кроме того, при отладке в последнем столбце панели CPU в ряде случаев появляется поясняющая информация о данных, полученных в результате выполнения очередной команды.
Так при отладки рассматриваемой программы, после второго прохождения команды в строке с адресом 0040103F (которая заносит код очередной цифры в область данных исходного кода с именем numtxt), в комментарии к строке с адресом 00401038 появляется примечание ASCII "40". (Сама эта команда дизассемблирована в окне CPU как MOV BYTE PTR DS:[EBX],DL). Это примечание при очередном прохождении той же строки исходной команды
mov byte [ebx],dl
преобразуется в ASCII "409", при последующем прохождении - в ASCII "4096". Все это помогает разработчику быстро разобраться с формируемыми данными.
Отладчик OllyDbg имеет множество возможностей, познакомиться с которыми можно через его справочную систему. Уточним еще раз, что этот отладчик охватывает гораздо более широкий круг проблем отладки, чем требуется для простого программирования на ассемблере.
Задание. Построить библиотеку объектных модулей ОС Windows, содержащих доступные извне процедуры с именами atoi, itoa, lfwrit. Процедура atoi должна выполнять преобразование последовательности цифр, заданной адресом, который содержится регистре eax, в двоичный код, выдаваемой ее также через регистр eax. Процедура itoa должна выводить в стандартный вывод последовательность десятичных цифр, представляющих исходное значение аргумента в регистре eax, заданное им в двоичном коде. Процедура lfwrite переводит экранный вывод на следующую строку. Основная часть программы должна содержать массив записей вида {число, текст} и по введенному значению текст выводить число из той записи, значение второго поля которой совпадает с этим текстом. Процедуру поиска такой записи на завершающей стадии разработки целесообразно также поместить в библиотеку объектных модулей. Основная часть программы должна использовать библиотеку объектных модулей.
Контрольные вопросы.
1.Какие языковые средства описания структур данных содержит язык Си?
2.Какие языковые средства описания структур данных содержит ассемблер NASM?
30