
- •Формулировка задания
- •Тексты программ
- •Irpc I,12345
- •Трансляция и компоновка
- •Анализ карты памяти
- •Анализ содержимого стека
- •Отладка программы
- •Тексты программ
- •0001:00000000 0001060EH .Text code
- •0003:000015C0 00001ac4h .Bss data
- •Анализ содержимого стека
- •00401253 Push ecx
- •5: .Code
- •0040101C 55 push ebp
- •00401022 56 Push esi
- •00401023 57 Push edi
Задание A6
При выполнении этого задания нужно написать главную программу на языке Си и подпрограмму на языке Ассемблера. В подпрограмме необходимо как можно шире использовать строковые операции. Ниже приводятся два примера выполнения задания: в 16-разрядной среде с использованием Borland C++ 5.0 и TASM; в 32-разрядной среде с использованием Visual C++ и MASM. Программы почти идентичны, особое внимание уделено их различиям. (Можно было реализовать 32-разрядные программы на основе среды Borland C++ и TASM32. Тогда различий меньше, и на это будет указано).
Задание нужно выполнять в 32-разрядной среде (16-разрядная среда представляет лишь исторический интерес). Но полезно предварительно ознакомиться с первым примером выполнения задания.
Рекомендуется выполнить все действия с программой-образцом, прежде чем переходить к написанию и отладке собственной программы. Особенно это касается 32-разрядной среды.
Формулировка задания
Общее задание. Написать головную программу на языке Си и подпрограмму на языке Ассемблера. Программа на Си осуществляет ввод исходных данных, вывод результатов и диагностических сообщений (на экран и в файл, имя которого задается в командной строке). Подпрограмма на языке Ассемблера проводит обработку исходных данных. Обязательно использование строковых команд. Размещать результат обработки в новой строке даже в тех случаях, когда возможно всю обработку проводить в исходной строке (например, удаление символов). В подпрограмме использовать хотя бы одну локальную переменную. Подпрограмма передает в головную программу номер ошибки в глобальной переменной user_errno.
1) Из карты памяти выяснить размер кода программы и адрес переменной user_errno. Сопоставить с адресом user_errno, полученным с помощью отладчика.
2) В момент нахождения в теле подпрограммы поместить в файл содержимое стекового кадра и интерпретировать его.
Отчет должен содержать исходные тексты программ, текст файла протокола, а также ответы на пункты 1 и 2.
Задание. В строке удалить все латинские буквы 'a' до первого вхождения заданного символа. Сосчитать количество удалений и длину новой строки. (Тексты сообщений, выдаваемых при особых ситуациях: "Заданный символ — буква 'a'", "Строка пуста", "Заданный символ не найден", "Заданный символ — первый в строке", "Буква 'a' в голове строки не найдена"}
16-разрядная среда: Borland C++ и Tasm.
Тексты программ
файл a6v0.c
#ifndef __SMALL__
#error "Необходимо транслировать в малой модели памяти"
#endif
#include <stdio.h>
#define MAX_SIZE 100 /* Предельная длина строки */
int DelFromString(char, char*, char*, int*);
int user_errno;
char old_str[MAX_SIZE], new_str[MAX_SIZE];
static char * message[]={"Заданный символ - буква 'a'",
"Строка пуста",
"Заданный символ не найден",
"Заданный символ - первый в строке",
"Буква 'a' в голове строки не найдена"};
int main(int argc, char** argv)
{ char symbol;
int count_of_deleting, len_of_new_str;
FILE *fp;
user_errno = 0;
puts("Введите строку");
gets(old_str);
puts("Введите символ, входящий в строку");
symbol = (char) getchar();
if( argc > 1) {
if ( (fp = fopen(argv[1], "a")) == NULL ) {
fprintf(stderr, "Невозможно открыть файл %s\n", argv[1]);
return 1;
}
fprintf( fp, "Строка: %s Символ: %c\n", old_str, symbol);
}
count_of_deleting = DelFromString(symbol, old_str,
new_str,&len_of_new_str);
if( !user_errno){
puts("\nНовая строка:");
puts(new_str);
printf("Количество удалений %u\n", count_of_deleting);
printf("Длина новой строки %u\n", len_of_new_str);
if( argc > 1) {
fprintf(fp,"\nНовая строка: %s",new_str);
fprintf(fp,"\nКоличество удалений = %u."
" Длина новой строки = %u\n\n",
count_of_deleting, len_of_new_str);
fclose(fp);
}
return 0;
}
else{
puts("\n");
puts( message[ user_errno - 1]);
if( argc > 1) {
fputs( "Ошибка: ", fp);
fputs( message[ user_errno - 1], fp);
fputs("\n\n", fp);
fclose(fp);
}
return user_errno;
}
}
В пояснении нуждается несколько строк.
__SMALL__— это предопределенное имя (memory-model macro). Если оно определено, то используется малая модель памяти. Если же попытаться при трансляции использовать большую модель памяти
tcc -ml a6v0.c sub6v0.asm будет выведено сообщение об ошибке (убедитесь в этом).
В строке symbol = (char) getchar();преобразование типа (casting) можно было опустить, но оно оставлено для наглядности.
Запуск программы на этапе отладки: D:\>a6v0.exe (файл протокола не создаем). После того, как убедились в работоспособности программы, запускаем ее, указывая в командной строке имя файла протокола
D:\>a6v0.exe prot.txt
Текст программы сопровождаем комментариями, в котором показываем состояние программы после выполнения серии инструкций (иначе, читая текст программы, трудно сообразить, что она делает). Этот комментарий поможет при отладке. Тест выбирается достаточно содержательным, чтобы на нем проверить задание, но простым, чтобы отладка не была громоздкой.
Файл sub6v0.asm
JUMPS
.MODEL small,C
EXTRN user_errno: WORD
PUBLIC DelFromString
.CODE
DelFromString PROC USES si di
ARG symbol:BYTE, old_str:WORD, new_str:WORD, len_of_new_str:WORD
LOCAL len_of_tail
; Тест: вход: строка 1abzpa
; символ z
; выход: строка 1bzpa
; длина новой строки: 5, кол-во удалений: 1
;
cmp symbol,'a' ; Заданный символ совпадает с 'a'?
je err1 ; Да — на err1
;
; Установка параметров для строковых операций
mov ax,@data ; Загрузить дополнительный сегментный регистр
mov es,ax ; адресом сегмента данных
cld ; Поиск в направлении возрастания адресов
;
; Определение длины строки
mov cx,0FFFFh ; В CX — максимальное беззнаковое целое
; (можно было mov cx,–1)
mov di,OFFSET old_str ; В DI — адрес исходной строки
mov al,0 ; В AL — терминатор строки
;
; +----------------------
; old_str¦ 1¦ a¦ b¦ z¦ p¦ a¦ 0¦ CX = FFFF , AL = 0
; +----------------------
; DI
;
repne scasb ; Поиск конца строки
;
; F E D C B A 9 8 - последняя цифра CX
; +-----------------------+
; ¦ 1¦ a¦ b¦ z¦ p¦ a¦ 0¦ ¦ CX = FFF8
; +-----------------------+
; DI
;
not cx ; В CX — (CX = 7)
dec cx ; длина строки (CX = 6)
jcxz err2 ; Строка нулевой длины — на err2
mov len_of_tail,cx ; Сохранить длину строки
mov bx,cx ;в локальной переменной и регистре bx
;
; Поиск первого вхождения заданного символа
mov di, OFFSET old_str
mov al, symbol ; (al = 'z')
repne scasb
;
; 6 5 4 3 2 - содержимое CX
; +-----------------------+
; ¦ 1¦ a¦ b¦ z¦ p¦ a¦ 0¦ ¦ CX = 2 ZF = 1
; +-----------------------+
; DI
;
jne err3 ;Если ZF=0, то символ не найден
dec di ; Вернуть указатель на искомый символ
cmp di, OFFSET old_str ; Голова строки пуста?
je err4 ; Да — на err4
sub bx,cx ; В BX — длина головы строки,
; включая заданный символ (BX = 6 – 2 = 4)
;
; Подготовка к копированию
mov si, OFFSET old_str
mov di, OFFSET new_str
xor dx,dx ; В DX — счетчик удалений
sub len_of_tail, bx ; В len_of_tail длина хвоста (6 – 4 = 2)
; Цикл копирования символов головы
n: dec bx ; Уменьшить счетчик символов головы
je p ; Голова скопирована — выходим из цикла
lodsb ; Загрузить текущий символ в аккумулятор
; и переместить указатель
cmp al,'a' ; Символ совпадает с 'a'?
jne c ; Нет — пишем в new_str
inc dx ; Да — увеличиваем счетчик
jmp n ; и переходим к обработке следующего символа
c: stosb ; Помещаем символ в new_str и перемещаем указатель
jmp n
; +-----------------------+
; old_str¦ 1¦ a¦ b¦ z¦ p¦ a¦ 0¦ ¦
; +-----------------------+
; SI
; +----------
; new_str¦ 1¦ b¦ ¦ DX=1
; +----------
; DI
;
p: and dx,dx ; Количество удалений нулевое?
jz err5 ; Да — на err5
;
; Копирование хвоста строки
mov cx, len_of_tail ; (CX = 2)
inc cx ; Нужно переслать также заданный символ
inc cx ; и терминатор строки (CX = 4)
rep movsb
;
; +-----------------------+
; old_str¦ 1¦ a¦ b¦ z¦ p¦ a¦ 0¦ ¦
; +-----------------------+
; SI
; +--------------------+
; new_str¦ 1¦ b¦ z¦ p¦ a¦ 0¦ ¦
; +--------------------+
; DI
; Вычисление длины новой строки
dec di ; Скорректируем указатель
sub di, OFFSET new_str ; Вычтем начальный адрес (DI = 5)
; Возврат длины новой строки
mov si, OFFSET len_of_new_str
mov [si],di
; Возврат количества удалений
mov ax,dx
return:
ret
; Обработка ошибок