Stack Operations
A stack data structure
-
New values are added to the top of the stack, and existing values are removed from the top
-
A stack is also called a LIFO structure (Last-In, First-Out) because the last value put into the stack is always the first value taken out
- In this course, we concentrate specifically on the runtime stack
- It is supported directly by hardware in the CPU
- It is an essential part of the mechanism for calling and returning from procedures
- The runtime stack works at the system level to handle subroutine calls
- But the stack data structure is a programming construct typically written in a high-level programming language such as C++ or Java
- It is used when implementing algorithms that depend on last-in, first-out operations
스택 데이터 구조
- 새로운 값은 스택의 맨 위에 추가되고, 기존 값은 스택의 맨 위에서 제거됩니다.
- 스택은 마지막에 스택에 넣은 값이 항상 먼저 나오기 때문에 LIFO(Last-In, First-Out) 구조라고도 합니다.
- 이 과정에서 우리는 런타임 스택에 특히 집중합니다.
- 런타임 스택은 CPU에서 하드웨어로 직접 지원됩니다.
- 프로시저 호출 및 반환을 위한 메커니즘의 핵심 부분입니다.
- 런타임 스택은 시스템 수준에서 서브루틴 호출을 처리합니다.
- 스택 데이터 구조는 C++이나 Java와 같은 고급 프로그래밍 언어로 작성된 프로그래밍 구조입니다.
- 이는 LIFO 작업에 의존하는 알고리즘을 구현할 때 사용됩니다.
1.Runtime Stack (32-Bit Mode)
- A memory array managed directly by the CPU, using the ESP (stack pointer) register
- In 32-bit mode, ESP register holds a 32-bit offset into some location on the stack
- We rarely manipulate ESP directly
- Instead, it is indirectly modified by instructions such as CALL, RET, PUSH, and POP
- ESP always points to the last value to be added to, or pushed on, the top of stack
- In the figure shown below, the ESP contains hexadecimal 00001000, the offset of the most recently pushed value (00000006)
- In the diagrams, the top of the stack moves downward when the stack pointer decreases in value:
1.런타임 스택 (32비트 모드)
- CPU가 직접 관리하는 메모리 배열로, ESP(스택 포인터) 레지스터를 사용합니다.
- 32비트 모드에서 ESP 레지스터는 스택의 어떤 위치에 대한 32비트 오프셋을 저장합니다.
- 우리는 거의 ESP를 직접 조작하지 않습니다.
- 대신 CALL, RET, PUSH, POP과 같은 명령어에 의해 간접적으로 수정됩니다.
- ESP는 항상 스택의 맨 위에 추가되거나, 푸시된 마지막 값을 가리킵니다.
Push Operation
- 1.Decrements the stack pointer by 4
- 2.Copies a value into the location in the stack pointed to by the stack pointer
- The below figure shows the effect of pushing 000000A5 on a stack that already contains one value
- The ESP register always points to the last item pushed on the stack
- The runtime stack grows downward in memory, from higher addresses to lower addresses
- Before the push, ESP = 00001000h; after the push, ESP = 00000FFCh
- The rightmost figure shows the same stack after pushing a total of four integers
푸시 작업
1.스택 포인터를 4씩 감소시킵니다.
2.스택 포인터가 가리키는 스택 위치에 값을 복사합니다.
- 아래 그림은 이미 하나의 값이 있는 스택에 000000A5를 푸시한 결과를 보여줍니다.
- ESP 레지스터는 항상 스택에 푸시된 마지막 항목을 가리킵니다.
- 런타임 스택은 메모리에서 높은 주소에서 낮은 주소로 성장합니다.
- 푸시 전, ESP = 00001000h; 푸시 후, ESP = 00000FFCh 입니다.
- 가장 오른쪽 그림은 총 네 개의 정수를 푸시한 후의 동일한 스택을 보여줍니다.
Pop Operation
- 1.Removes a value from the stack
- 2.Increments the stack pointer (by the stack element size) to point to the next-highest location in the stack
- The below figure shows the stack before and after the value 00000002 is popped
- The area of the stack below ESP is logically empty, and will be overwritten the next time the current program executes any instruction that pushes a value on the stack
팝 작업
1.스택에서 값을 제거합니다.
2.스택 포인터를 증가시켜(스택 요소 크기만큼) 스택에서 다음 가장 높은 위치를 가리킵니다.
- 아래 그림은 값 00000002가 팝되기 전과 후의 스택을 보여줍니다.
- ESP 아래의 스택 영역은 논리적으로 비어 있으며, 현재 프로그램이 스택에 값을 푸시하는 명령어를 다음 번 실행할 때 덮어씌워집니다.
스택 응용
- 스택은 레지스터가 여러 목적으로 사용될 때 편리한 임시 저장 영역을 제공합니다.
- CALL 명령어가 실행되면, CPU는 스택에 현재 서브루틴의 반환 주소를 저장합니다.
- 서브루틴을 호출할 때, 스택에 푸시하여 인수라고 하는 입력 값을 전달합니다.
- 스택은 서브루틴 내부의 지역 변수에 대한 임시 저장소를 제공합니다.
Stack Applications
- A stack makes a convenient temporary save area for registers when they are used for more than one purpose
- After they are modified, they can be restored to their original values
- When the CALL instruction executes, the CPU saves the current subroutine’s return address on the stack
- When calling a subroutine, you pass input values called arguments by pushing them on the stack
- The stack provides temporary storage for local variables inside subroutines
2.PUSH and POP Instructions
PUSH Instruction
- 1.Decrements ESP
- A 16-bit operand causes ESP to be decremented by 2
- A 32-bit operand causes ESP to be decremented by 4
- 2.Then copies a source operand into the stack
- Instruction format
PUSH reg/mem16
PUSH reg/mem32
PUSH imm32
PUSH 명령어
1.ESP를 감소시킵니다.
- 16비트 피연산자는 ESP가 2씩 감소되게 합니다.
- 32비트 피연산자는 ESP가 4씩 감소되게 합니다.
2.그런 다음 스택에 소스 피연산자를 복사합니다.
- 명령어 형식
POP Instruction
- 1.Copies the contents of the stack element pointed to by ESP into a 16- or 32-bit destination operand
- 2.Then increments ESP
- If the operand is 16 bits, ESP is incremented by 2
- If the operand is 32 bits, ESP is incremented by 4
- Instruction format
POP reg/mem16
POP reg/mem32
POP 명령어
1.ESP가 가리키는 스택 요소의 내용을 16- 또는 32비트 대상 피연산자에 복사합니다.
2.그런 다음 ESP를 증가시킵니다.
- 피연산자가 16비트인 경우, ESP가 2씩 증가됩니다.
- 피연산자가 32비트인 경우, ESP가 4씩 증가됩니다.
- 명령어 형식
PUSHFD and POPFD Instructions
- PUSHFD instruction pushes the 32-bit EFLAGS register on the stack
- POPFD instruction pops the stack into EFLAGS
- MOV cannot be used to copy the flags to a variable, so PUSHFD is the best way to save the flags
- Sometimes it is useful to make a backup copy of the flags so you can restore them to their former values later
- Often, we enclose a block of code within PUSHFD and POPFD
pushfd ; save the flags
;
; any sequence of statements here...
; l restore the flags
popfd
push saveFlags ; push saved flag values
popfd ; copy into the flags
PUSHFD와 POPFD 명령어
- PUSHFD 명령어는 32비트 EFLAGS 레지스터를 스택에 푸시합니다.
- POPFD 명령어는 스택을 EFLAGS로 팝합니다.
- MOV는 플래그를 변수에 복사하는 데 사용할 수 없으므로, 플래그를 저장하는 데 PUSHFD가 가장 좋은 방법입니다.
- 나중에 이전 값으로 복원할 수 있도록 플래그의 백업 복사본을 만드는 것이 유용한 경우도 있습니다.
- 종종 코드 블록을 PUSHFD와 POPFD 사이에 넣습니다.
- 프로그램의 실행 경로가 POPFD 명령어를 건너뛰지 않도록 주의하세요.
- 프로그램이 시간이 지남에 따라 수정될 때, 푸시와 팝이 모두 어디에 있는지 기억하는 것이 까다로울 수 있습니다.
- 플래그를 저장하고 복원하는 덜 오류가 발생하는 방법
- 스택에 푸시한 다음 변수로 즉시 팝하는 것입니다.
PUSHAD, PUSHA, POPAD, and POPA
- PUSHAD pushes all of the 32-bit general-purpose registers on the stack in the following order:
- EAX, ECX, EDX, EBX, ESP (value before executing PUSHAD), EBP, ESI, and EDI
- The POPAD instruction pops the same registers off the stack in reverse order
- 16-bit versions
- PUSHA pushes the 16-bit general-purpose registers on the stack in the following order:
- » AX, CX, DX, BX, SP, BP, SI, DI
- The POPA instruction pops the same registers in reverse
- You should only use PUSHA and POPA when programming in 16-bit mode
- If you write a procedure that modifies a number of 32-bit registers, use PUSHAD at the beginning of the procedure and POPAD at the end to save and restore the registers
MySub PROC
pushed ; save general-purpose registers
.
.
mov eax,...
mov edx,...
mov ecx,...
.
.
popad ; restore general-purpose registers
ret
MySub ENDP
PUSHAD, PUSHA, POPAD, POPA
- PUSHAD는 32비트 범용 레지스터를 다음 순서로 스택에 푸시합니다:
- EAX, ECX, EDX, EBX, ESP (PUSHAD 실행 전의 값), EBP, ESI, EDI
- POPAD 명령어는 스택에서 동일한 레지스터를 역순으로 팝합니다.
- 16비트 버전:
- PUSHA는 16비트 범용 레지스터를 다음 순서로 스택에 푸시합니다:
- AX, CX, DX, BX, SP, BP, SI, DI
- POPA 명령어는 역순으로 동일한 레지스터를 팝합니다.
- 16비트 모드에서 프로그래밍할 때만 PUSHA와 POPA를 사용해야 합니다.
- 여러 개의 32비트 레지스터를 수정하는 프로시저를 작성할 때, 프로시저 시작 부분에 PUSHAD를 사용하고 끝 부분에 POPAD를 사용하여 레지스터를 저장 및 복원합니다.
- 예외: 하나 이상의 레지스터에서 결과를 반환하는 프로시저는 PUSHA와 PUSHAD를 사용하지 않아야 합니다.
- 예를 들어, 다음 ReadValue 프로시저는 EAX에서 정수를 반환합니다.
- POPAD 호출은 EAX에서 반환되는 값을 덮어씁니다.
Example: Reversing a String
- The following program loops through a string and pushes each character on the stack
- It then pops the letters from the stack (in reverse order) and stores them back into the same string variable
- Because the stack is a LIFO (last-in, first-out) structure, the letters in the string are reversed
; Reversing a String (RevStr.asm)
.386
.model flat,stdcall
.stack 4096
ExitProcess PROTO,dwExitCode:DWORD
.data
Aname BYTE "Abraham Lincoln",0
nameSize = {$ - aName} - 1
.code
main PROC
; Push the name on the stack.
mov ecx.nameSize
mov esi,0
L1: movzx eax,aName[esi] ; get character
Push eax ; push on stack
inc esi
loop L1
; Pop the name from the stack, in reverse,
; and store in the aName array
mov ecx,nameSize
mov esi,0
L2: pop eax ; get character
mov aName[esi],ai ; store in string
inc esi
loop L2
INVOKE ExitProcess,0
main ENDP
END main
예제: 문자열 뒤집기
- 다음 프로그램은 문자열을 반복하며 각 문자를 스택에 푸시합니다.
- 그런 다음 스택에서 문자를 팝하여 (역순으로) 동일한 문자열 변수에 다시 저장합니다.
- 스택이 후입선출(LIFO, Last-In, First-Out) 구조이기 때문에 문자열의 문자가 뒤집힙니다.
이 프로그램은 문자열 "Abraham Lincoln"을 뒤집어서 "nocnilK maharbA"가 되도록 합니다. 문자열의 각 문자를 스택에 푸시한 다음 역순으로 팝하여 원래 문자열 변수에 다시 저장하면, 문자열이 뒤집힌 순서로 저장됩니다. 이 방법은 스택이 LIFO 구조이기 때문에 작동합니다.
Defining and Using Procedures
- In assembly language, we typically use the term procedure to mean a subroutine
정의 및 프로시저 사용
- 어셈블리 언어에서는 보통 서브루틴을 의미하는 용어로 프로시저를 사용합니다.
1.PROC Directive
Defining a Procedure
- Informally, we can define a procedure as a named block of statements that ends in a return statement
- A procedure is declared using the PROC and ENDP directives
1.PROC 지시문
프로시저 정의하기
- 비공식적으로, 프로시저를 반환문이 있는 명명된 문장 블록으로 정의할 수 있습니다.
- 프로시저는 PROC 및 ENDP 지시문을 사용하여 선언됩니다.
- 유효한 식별자로 이름을 지정해야 합니다.
- 프로그램의 시작 프로시저 이외의 프로시저를 만들 때 RET 명령어로 프로시저를 종료해야 합니다.
- RET는 CPU가 프로시저가 호출된 위치로 되돌아가도록 강제합니다.
Labels in Procedures
- By default, labels are visible only within the procedure in which they are declared
- This rule often affects jump and loop instructions
- E.g.) The label named Destination must be located in the same procedure as the JMP instruction:
jmp Destination
- It is possible to work around this limitation by declaring a global label, identified by a double colon (::)
after its name:
Destination::
- In terms of program design, it’s not a good idea to jump or loop outside of the current procedure
- Procedures have an automated way of returning and adjusting the runtime stack
- If you directly transfer out of a procedure, the runtime stack can easily become corrupted
프로시저 내의 레이블
- 기본적으로 레이블은 선언된 프로시저 내에서만 표시됩니다.
- 이 규칙은 주로 점프 및 루프 명령에 영향을 줍니다.
- 예를 들어, Destination이라는 레이블은 JMP 명령과 동일한 프로시저에 있어야 합니다.
- 이 제한을 해결하기 위해 이름 뒤에 이중 콜론(::)을 사용하여 전역 레이블을 선언할 수 있습니다.
- 프로그램 설계 관점에서 현재 프로시저 외부로 점프하거나 루프하는 것은 좋지 않습니다.
- 프로시저는 런타임 스택을 조정하고 반환하는 자동 방법을 가지고 있습니다.
- 프로시저에서 직접 전송하면 런타임 스택이 손상될 수 있습니다.
Example: SumOf Three Integers
- This example calculates the sum of three 32-bit integers
- We will assume that relevant integers are assigned to EAX, EBX, and ECX before the procedure is called
- The procedure returns the sum in EAX
Sum of PROC
add eax, ebx
add eax, ecx
ret
SumOf ENDP
예제: 세 개의 정수 합
- 이 예제에서는 세 개의 32비트 정수의 합을 계산합니다.
- 프로시저가 호출되기 전에 관련 정수가 EAX, EBX, ECX에 할당되었다고 가정합니다.
- 프로시저는 EAX에 합을 반환합니다.
Documenting Procedures
- Adding clear and readable documentation to your programs is a good habit
- A few suggestions for information that you can put at the beginning of each procedure
- A description of all tasks accomplished by the procedure
- A list of input parameters and their usage, labeled by a word such as ‘Receives’
- » If any input parameters have specific requirements for their input values, list them here
- A description of any values returned by the procedure, labeled by a word such as ‘Returns’
- A list of any special requirements, called preconditions, that must be satisfied before the procedure is called
- » These can be labeled by the word ‘Requires’
- » For example, for a procedure that draws a graphics line, a useful precondition would be that the video display adapter must already be in graphics mode
;---------------------------------------
;sumof
;
;Calculates and returns the sum of three 32-bit integers
;Receives: EAX, EBX, ECX, the three integers. May be signed or unsigned.
;
;Returns: EAX = sum
---------------------------------------
SumOf Proc
add eax,ebx
add eax,ecx
ret
SumOf ENDP
프로시저 문서화
- 프로그램에 명확하고 읽기 쉬운 문서를 추가하는 것은 좋은 습관입니다.
- 각 프로시저 시작 부분에 넣을 수 있는 정보 몇 가지 제안 사항입니다.
- 프로시저가 수행하는 모든 작업에 대한 설명
- 'Receives'라는 단어로 표시된 입력 매개변수 목록과 사용법
- 입력값에 대한 구체적인 요구사항이 있는 경우 여기에 나열합니다.
- 'Returns'라는 단어로 표시된 프로시저에서 반환되는 값에 대한 설명
- 프로시저가 호출되기 전에 충족해야 하는 특별한 요구사항 목록
- 'Requires'라는 단어로 표시할 수 있습니다.
- 예를 들어, 그래픽 선을 그리는 프로시저의 경우, 유용한 전제 조건은 비디오 디스플레이 어댑터가 이미 그래픽 모드에 있어야 한다는 것입니다.
2.CALL and RET Instructions
The CALL instruction
- This instruction calls a procedure by directing the processor to begin execution at a new memory location
CALL 명령어
- 이 명령어는 프로시저를 호출하여 프로세서가 새 메모리 위치에서 실행을 시작하도록 지시합니다.
The RET (return from procedure) instruction
- The procedure uses a RET instruction to bring the processor back to the point in the program where theprocedure was called
RET (프로시저 반환) 명령어
- 프로시저는 RET 명령어를 사용하여 프로시저가 호출된 프로그램 지점으로 프로세서를 되돌립니다.
Mechanical point of view
- 1.The CALL instruction pushes its return address on the stack
- 2.The CALL instruction copies the called procedure’s address into the instruction pointer
- 3.(When the procedure is ready to return) RET instruction pops the return address from the stack into the instruction pointer
- In 32-bit mode, the CPU executes the instruction in memory pointed to by EIP (instruction pointer register)
기계적 관점
- CALL 명령어는 반환 주소를 스택에 푸시합니다.
- CALL 명령어는 호출된 프로시저의 주소를 명령 포인터에 복사합니다.
- (프로시저가 반환할 준비가 되면) RET 명령어는 스택에서 반환 주소를 명령 포인터로 팝니다.
- 32비트 모드에서 CPU는 EIP(명령 포인터 레지스터)가 가리키는 메모리의 명령을 실행합니다.
Call and Return Example
- Suppose that in main, a CALL statement is located at offset 00000020
- Typically, this instruction requires 5 bytes of machine code, so the next statement (a MOV in this case) is located at offset 00000025
- Next, suppose that the first executable instruction in MySub is located at offset 00000040
- When the CALL instruction executes, the address following the call (00000025) is pushed on the stack and the address of MySub is loaded into EIP
- All instructions in MySub execute up to its RET instruction
- When the RET instruction executes, the value in the stack pointed to by ESP is popped into EIP
- Then, ESP is incremented so it points to the previous value on the stack
호출 및 반환 예제
- main에서 CALL 문이 오프셋 00000020에 위치한다고 가정해봅시다.
- 일반적으로 이 명령어는 기계어로 5 바이트가 필요하므로 다음 문장(이 경우 MOV)은 오프셋 00000025에 위치합니다.
- 다음으로, MySub의 첫 번째 실행 가능한 명령이 오프셋 00000040에 위치한다고 가정합니다.
- CALL 명령어가 실행되면 호출 뒤의 주소(00000025)가 스택에 푸시되고 MySub의 주소가 EIP에 로드됩니다.
- MySub의 모든 명령어는 RET 명령어까지 실행됩니다.
- RET 명령어가 실행되면, ESP가 가리키는 스택의 값이 EIP로 팝됩니다.
- 그런 다음 ESP가 증가하여 스택의 이전 값에 포인터가 위치하게 됩니다.
3.Nested Procedure Calls
- A nested procedure call occurs when a called procedure calls another procedure before the first procedure returns
- Suppose that main calls a procedure named Sub1
- While Sub1 is executing, it calls the Sub2 procedure
- While Sub2 is executing, it calls the Sub3 procedure
- When the RET instruction at the end of Sub3 executes, it pops the value at stack[ESP] into the instruction pointer
- This causes execution to resume at the instruction following the call Sub3 instruction
- After the return, ESP points to the next-highest stack entry...
- When Sub1 returns, stack[ESP] is popped into the instruction pointer, and execution resumes in main
- The stack proves itself a useful device for remembering information, including nested procedure calls
3.중첩된 프로시저 호출
- 중첩된 프로시저 호출이란 첫 번째 프로시저가 반환되기 전에 호출된 프로시저가 다른 프로시저를 호출하는 경우를 말합니다.
- 예를 들어, main이 Sub1이라는 프로시저를 호출한다고 가정해봅시다.
- Sub1이 실행되는 동안, Sub1은 Sub2 프로시저를 호출합니다.
- Sub2가 실행되는 동안, Sub2는 Sub3 프로시저를 호출합니다.
- Sub3 끝부분의 RET 명령어가 실행될 때, stack[ESP]의 값을 명령 포인터로 팝합니다.
- 이로 인해 실행이 Sub3 호출 명령어 다음의 명령어에서 재개됩니다.
- 반환 후에는 ESP가 다음으로 높은 스택 항목을 가리킵니다...
- Sub1이 반환되면, stack[ESP]가 명령 포인터로 팝되고, main에서 실행이 재개됩니다.
- 스택은 중첩된 프로시저 호출을 포함한 정보를 기억하는 데 유용한 장치로 입증됩니다.
4. Passing Register Arguments to Procedures
- If you write a procedure that performs some standard operation such as calculating the sum of an integer array, it’s not a good idea to include references to specific variable names inside the procedure
- If you did, the procedure could only be used with one array
- A better approach is to pass the offset of an array to the procedure and pass an integer specifying the number of array elements
- We call these arguments (or input parameters)
- In assembly language, it is common to pass arguments inside general-purpose registers
- Prev. example: SumOf procedure that added the integers in the EAX, EBX, and ECX registers
- In main, before calling SumOf, we assign values to EAX, EBX, and ECX
.data
theSum DWORD ?
.code
main PROC
mov eax,1000h ;argument
mov ebx,2000h ;argument
mov ecx,3000h ;argument
call Sumof ;EAX = ( EAX + EBX + ECX)
mov theSum,eax ;save the sum
4.프로시저에 레지스터 인수 전달
- 정수 배열의 합을 계산하는 등의 표준 작업을 수행하는 프로시저를 작성할 때 프로시저 내에서 특정 변수 이름에 대한 참조를 포함하는 것은 좋은 방법이 아닙니다.
- 그렇게 하면 프로시저는 하나의 배열에서만 사용할 수 있습니다.
- 더 나은 접근 방식은 배열의 오프셋을 프로시저에 전달하고 배열 요소 수를 지정하는 정수를 전달하는 것입니다.
- 이러한 인수(또는 입력 매개변수)라고 합니다.
- 어셈블리 언어에서는 일반적으로 범용 레지스터 내에서 인수를 전달합니다.
- 이전 예제: EAX, EBX, ECX 레지스터의 정수를 더하는 SumOf 프로시저
- main에서 SumOf를 호출하기 전에 EAX, EBX, ECX에 값을 할당합니다.
5. Example: Summing an Integer Array
- ArraySum that receives two parameters from a calling program:
- A pointer to an array of 32-bit integers, and a count of the number of array values
- This procedure calculates and returns the sum of the array in EAX
- Nothing in this procedure is specific to a certain array name or array size
;----------------------------------------------------
;ArraySum
;
;Calculates the sum of an array of 32-bit integers
;Receives: ESI = the array offset
; ECX = number of elements in the array
;Returns: EAX = sum of the array elements
;----------------------------------------------------
ArraySum PROC
push esi ; save ESI, ECX
push ecx
mov eax,0 ; set the sum to zero
L1: add eax,[esi] ; add each integer to sum
add esi,TYPE DWORD ; point to next integer
loop L1 ; repeat for array size
pop ecx ; restore ECX, ESI
pop esi
ret ; sum is in EAX
ArraySum ENDP
5.예제: 정수 배열의 합 구하기
- 호출 프로그램에서 두 개의 매개변수를 받는 ArraySum이라는 프로시저를 만들어 봅시다:
- 32비트 정수 배열의 포인터와 배열 값의 개수입니다.
- 이 프로시저는 배열의 합을 EAX에 반환합니다.
- 이 프로시저는 특정 배열 이름이나 배열 크기와 관련이 없습니다.
위의 코드는 ArraySum 프로시저를 정의하고 있습니다. 이 프로시저는 32비트 정수 배열의 합을 계산합니다. 프로시저는 ESI 레지스터에 배열의 오프셋을 받고, ECX 레지스터에 배열 요소의 개수를 받습니다. 결과값인 배열 요소의 합은 EAX 레지스터에 반환됩니다.
프로시저는 ESI와 ECX 레지스터를 스택에 저장한 후, 배열의 각 정수를 더해 합계를 구합니다. 이 때, ESI 레지스터는 다음 정수를 가리키도록 업데이트되고, 배열의 크기만큼 반복됩니다. 반복이 끝나면 ECX와 ESI 레지스터를 복원하고 결과값이 저장된 EAX 레지스터를 반환합니다.
Testing the ArraySum Procedure
- The following program tests the ArraySum procedure by calling it and passing the offset and length of an array of 32-bit integers
- After calling ArraySum, it saves the procedure’s return value in a variable named theSum
; Testing the ArraySum procedure (TestArraySum.asm)
.386
.model flat, stdcall
.sttack 4096
ExitProcess PROTO, dwExitCode:DWORD
.data
array DWORD 10000h,20000h,30000h,40000h,50000h
theSum DWORD ?
.code
main PROC
mov esi,OFFSET array ; ESI points to array
mov ecx,LENGTHOF array ; ECX = array count
call ArraySum ; calculate the sum
mov theSum,eax ; returned in EAX
INVOKE ExitProcess,0
main ENDP
;----------------------------------------------------
;ArraySum
;
;Calculates the sum of an array of 32-bit integers
;Receives: ESI = the array offset
; ECX = number of elements in the array
;Returns: EAX = sum of the array elements
;----------------------------------------------------
ArraySum PROC
push esi ; save ESI, ECX
push ecx
mov eax,0 ; set the sum to zero
L1: add eax,[esi] ; add each integer to sum
add esi,TYPE DWORD ; point to next integer
loop L1 ; repeat for array size
pop ecx ; restore ECX, ESI
pop esi
ret ; sum is in EAX
ArraySum ENDP
ArraySum 프로시저 테스트
다음 프로그램은 ArraySum 프로시저를 호출하여 32비트 정수 배열의 오프셋과 길이를 전달함으로써 ArraySum 프로시저를 테스트합니다. ArraySum을 호출한 후에는 theSum이라는 변수에 프로시저의 반환 값을 저장합니다.
위의 코드는 ArraySum 프로시저를 호출하는 메인 프로시저를 보여줍니다. 메인 프로시저는 ESI 레지스터에 배열의 오프셋을 할당하고, ECX 레지스터에 배열의 요소 수를 할당합니다. 그런 다음 ArraySum 프로시저를 호출하여 배열의 합을 계산하고, 결과값을 theSum 변수에 저장합니다.
6. Saving and Restoring Registers
- In the ArraySum example, ECX and ESI were pushed on the stack at the beginning of the procedure and popped at the end
- This action is typical of most procedures that modify registers
- Always save and restore registers that are modified by a procedure so the calling program can be sure that none of its own register values will be overwritten
- The exception to this rule pertains to registers used as return values, usually EAX
6.레지스터 저장 및 복원
ArraySum 예제에서는 프로시저 시작 시 ECX와 ESI 레지스터를 스택에 푸시하고, 프로시저가 끝날 때 팝하였습니다. 이러한 작업은 대부분의 프로시저에서 수정된 레지스터를 처리하는 일반적인 방법입니다.
프로시저에 의해 수정된 레지스터를 항상 저장하고 복원하여 호출 프로그램이 자체 레지스터 값이 덮어쓰여지지 않음을 확신할 수 있습니다. 이 규칙의 예외는 일반적으로 EAX로 사용되는 반환 값 레지스터에 관한 것입니다. 이들은 푸시 및 팝하지 않습니다.
USES Operator
- The USES operator, coupled with the PROC directive, lets you list the names of all registers modified within a procedure
- USES tells the assembler to do two things
- First, generate PUSH instructions that save the registers on the stack at the beginning of the procedure
- Second, generate POP instructions that restore the register values at the end of the procedure
- The USES operator immediately follows PROC, and is itself followed by a list of registers on the same line separated by spaces or tabs (not commas)
Array sum PROC USES esi ecx
mov eax,0 ; set the sum to zero
L1:
add eax,[esi] ; add each integer to sum
add esi,TYPE DWORD ; point to next integer
loop L1 ; repeat for array size
ret ; sum is in EAX
ArraySum ENDP
Right-click on the code in a debugging session and select [Go to Disassembly], then you can view the hidden machine instructions generated by MASM's advanced operators and directives
ArraySum Proc
push esi
push ecx
mov eax,0
L1:
add eax,[esi]
add esi,TYPE DWORD
loop L1
pop ecx
pop esi
ret
ArraySum ENDP
USES 연산자
USES 연산자는 프로시저 지시문과 함께 사용되어 프로시저 내에서 수정되는 모든 레지스터의 이름을 나열할 수 있게 합니다. USES는 어셈블러에게 두 가지 작업을 수행하도록 지시합니다.
- 프로시저 시작 부분에서 스택에 레지스터를 저장하는 PUSH 명령을 생성합니다.
- 프로시저 종료 부분에서 레지스터 값을 복원하는 POP 명령을 생성합니다.
USES 연산자는 PROC 바로 다음에 오며, 스페이스 또는 탭으로 구분된 레지스터 목록이 연산자 뒤에 나타납니다(쉼표가 아님).
예를 들어, 아래와 같이 ArraySum 프로시저를 수정할 수 있습니다.
이렇게 작성하면 어셈블러는 프로시저 시작 부분에서 ESI와 ECX 레지스터를 스택에 저장하는 PUSH 명령과 프로시저 종료 부분에서 레지스터 값을 복원하는 POP 명령을 자동으로 생성합니다. 디버깅 세션 중 코드에서 마우스 오른쪽 버튼을 클릭하여 [디스어셈블리로 이동]을 선택하면, MASM의 고급 연산자 및 지시문에 의해 생성된 숨겨진 기계 명령을 볼 수 있습니다.
이렇게 하면 ArraySum 프로시저가 ESI와 ECX 레지스터를 스택에 저장하고 복원하는 동작이 자동으로 처리되며, 프로시저 호출 프로그램이 레지스터 값이 덮어쓰여지지 않도록 보호할 수 있습니다.
Exception
- There is an important exception when a procedure returns a value in a register (usually EAX)
- In this case, the return register should not be pushed and popped
- E.g.) In the SumOf procedure in the following example, it pushes and pops EAX, causing the procedure’s return value to be lost:
SumOf PROC ; sum of three integers
push eax ; save EAX
add eax,ebx ; calculate the sum
add eax,ecx ; of EAX, EBX, ECX
pop eax ; lost the sum!
ret
이 예제에서는 프로시저가 레지스터(보통 EAX)에 값을 반환하는 경우 중요한 예외가 있습니다. 이 경우에는 반환 레지스터를 push하고 pop하지 않아야 합니다.
예를 들어, 다음 예제의 SumOf 프로시저에서는 EAX를 push하고 pop하여 프로시저의 반환 값이 손실됩니다.
위의 예제에서는 EAX 레지스터에 반환 값을 저장하는데, 프로시저 시작 부분에서 EAX 레지스터를 스택에 push하고 종료 부분에서 pop하면서 반환 값이 손실됩니다. 이 경우 EAX 레지스터는 push 및 pop하지 않고 원래 값을 유지해야 합니다. 다음과 같이 코드를 수정할 수 있습니다.
이렇게 하면 EAX 레지스터에 저장된 반환 값이 손실되지 않고 호출 프로그램으로 올바르게 전달됩니다. 이 예외를 기억하고 반환 값이 있는 프로시저를 작성할 때 반환 레지스터를 push 및 pop하지 않도록 주의해야 합니다.
중간고사 범위는 여기까지
Linking to an External Library
Link library
- A file containing procedures (subroutines) that have been assembled into machine code
- A link library begins as one or more source files, which are assembled into object files
- The object files are inserted into a specially formatted file recognized by the linker utility
Suppose a program displays a string in the console window by calling a procedure named WriteString
- The program source must contain a PROTO directive identifying the WriteString procedure: WriteString proto
- Next, a CALL instruction executes WriteString: call WriteString
- When the program is assembled, the assembler leaves the target address of the CALL instruction blank, knowing that it will be filled in by the linker
- The linker looks for WriteString in the link library and copies the appropriate machine instructions from the library into the program’s executable file
- In addition, it inserts WriteString’s address into the CALL instruction
- If a procedure you’re calling is not in the link library, the linker issues an error message and does not generate an executable file
Linker Command Options
- The linker utility combines a program’s object file with one or more object files and link libraries
link hello.obj irvine32.lib kernel32.lib
- E.g.) The following command links hello.obj to the irvine32.lib and kernel32.lib libraries
Linking 32-Bit Programs
- The kernel32.lib file (part of the Microsoft Windows Platform Software Development Kit) contains linking
information for system functions located in a file named kernel32.dll
- The latter is a fundamental part of MS-Windows, and is called a dynamic link library
- It contains executable functions that perform character-based input–output
- In this course, our programs link Irvine32.lib
The Irvine32 Library
1. Motivation for Creating the Library
- There is no MS official standard library for assembly language programming
- When programmers first started writing assembly language for x86 processors in the early 1980s, MS-DOS was the commonly used OS
- These 16-bit programs were able to call MS-DOS functions (known as INT 21h services) to do simple input/output
- Even at that time, if you wanted to display an integer on the console, you had to write a fairly complicated procedure that converted from the internal binary representation of integers to a sequence of ASCII characters that would display the integer on the screen
- We called it WriteInt, and this is the logic, abstracted into pseudocode:
- In 32-bit mode running under Windows, an IO library must make calls directly into the OS
- But, it presents some challenges for beginning programmers
- Therefore, the Irvine32 library is designed to provide a simple interface for IO for beginners
2. Overview
Console Window
- The console window (or command window) is a text-only window created by MS-Windows when a
command prompt is displayed
- To display a console window in Microsoft Windows, Type cmd into the Start Search field
A file handle
- A 32-bit integer used by the Windows OS to identify a file that is currently open
- When your program calls a Windows service to open or create a file, the OS creates a new file handle and makes it available to your program
- Each time you call an OS service method to read from or write to the file, you must pass the same file handle as a parameter to the service method
How to use the Irvine32 library in MS Visual Studio
- Download Irvine32.zip from the e-class and extract it into certain path (here, we assume C:\ Irvine32)
- At the top of your MASM program, type ‘INCLUDE C:\ Irvine32\ Irvine32.inc’
- Right-click the project name in [Solution Explorer], click [Properties] – [Linker] – [General] – [Additional Library Directories], and enter the path of irvine32 library
- Right-click the project name in [Solution Explorer], click [Properties] – [Linker] – [Input] – [Additional Dependencies], and type ‘irvine32.lib
3. Individual Procedure Descriptions
CreateOutputFile
- The CreateOutputFile procedure creates a new disk file and opens it for writing
- When you call the procedure, place the offset of a filename in EDX
- When the procedure returns, EAX will contain a valid file handle (32-bit integer) if the file was created successfully
- Otherwise, EAX equals INVALID_HANDLE_VALUE (a predefined constant)
- The OpenInputFile procedure opens an existing file for input
- Pass it the offset of a filename in EDX
- When it returns, if the file was opened successfully, EAX will contain a valid file handle
- Otherwise, EAX will equal INVALID_HANDLE_VALUE (a predefined constant)
ReadFromFile
- The ReadFromFile procedure reads an input disk file into a memory buffer
- When you call ReadFromFile, pass it an open file handle in EAX, the offset of a buffer in EDX, and the maximum number of bytes to read in ECX
- When ReadFromFile returns, check the value of the Carry flag
- If CF is clear, EAX contains a count of the number of bytes read from the file
- But if CF is set, EAX contains a numeric system error code
WriteToFile
- The WriteToFile procedure writes the contents of a buffer to an output file
- Pass it a valid file handle in EAX, the offset of the buffer in EDX, and the number of bytes to write in ECX
- When the procedure returns, if EAX is greater than zero, it contains a count of the number of bytes written; otherwise, an error occurred
CloseFile
- The CloseFile procedure closes a file that was previously created or opened
- The file is identified by a 32-bit integer handle, which is passed in EAX
- If the file is closed successfully, the value returned in EAX will be non-zero
Clrscr
- The Clrscr procedure clears the console window
- This procedure is typically called at the beginning and end of a program
- If you call it at other times, you may need to pause the program by first calling WaitMsg
- Doing this allows the user to view information already on the screen before it is erased
Crlf
- The Crlf procedure advances the cursor to the beginning of the next line in the console window
- It writes a string containing the ASCII character codes 0Dh and 0Ah
Delay
- The Delay procedure pauses the program for a specified number of milliseconds
- Before calling Delay, set EAX to the desired interval
GetMaxXY
- The GetMaxXY procedure gets the size of the console window’s buffer
- If the console window buffer is larger than the visible window size, scroll bars appear automatically
- GetMaxXY has no input parameters
- When it returns, the DX register contains the number of buffer columns and AX contains the number of buffer rows
- The possible range of each value can be no greater than 255, which may be smaller than the actual window buffer size
Gotoxy
- The Gotoxy procedure locates the cursor at a given row and column in the console window
- By default, the console window’s X-coordinate range is 0 to 79 and the Y-coordinate range is 0 to 24
- When you call Gotoxy, pass the Y-coordinate (row) in DH and the X-coordinate (column) in DL
SetTextColor
- The SetTextColor procedure sets the foreground and background colors for text output
- When calling SetTextColor, assign a color attribute to EAX
- The following predefined color constants can be used for both foreground and background:
| | | |
---|
black = 0 | red = 4 | gray = 8 | lightRed = 12 |
blue = 1 | magenta = 5 | lightBlue = 9 | lightMagenta = 13 |
green = 2 | brown = 6 | lightGreen =10 | yellow = 14 |
cyan = 3 | lightGray = 7 | lightCyan = 11 | white = 15 |
- To get a complete color byte value, multiply the background color by 16 and add it to the foreground color
- The following statements set the color to white on a blue background:
GetTextColor
- The GetTextColor procedure gets the current foreground and background colors of the console window
- It has no input parameters
- It returns the background color in the upper four bits of AL and the foreground color in the lower four bits
IsDigit
- The IsDigit procedure determines whether the value in AL is the ASCII code for a valid decimal digit
- When calling it, pass an ASCII character in AL
- The procedure sets the Zero flag if AL contains a valid decimal digit; otherwise, it clears Zero flag
ParseDecimal32
- The ParseDecimal32 procedure converts an unsigned decimal integer string to 32-bit binary
- All valid digits occurring before a nonnumeric character are converted
- Leading spaces are ignored
- Pass it the offset of a string in EDX and the string’s length in ECX
- The binary value is returned in EAX
- If the integer is blank, EAX = 0 and CF = 1
- If the integer contains only spaces, EAX = 0 and CF = 1
- If the integer is larger than 232-1, EAX = 0 and CF = 1
- Otherwise, EAX contains the converted integer and CF = 0
ParseInteger32
- The ParseInteger32 procedure converts a signed decimal integer string to 32-bit binary
- All valid digits from the beginning of the string to the first nonnumeric character are converted
- Leading spaces are ignored
- Pass it the offset of a string in EDX and the string’s length in ECX
- The binary value is returned in EAX
Random32
- The Random32 procedure generates and returns a 32-bit random integer in EAX
- When called repeatedly, Random32 generates a simulated random sequence
- The numbers are created using a simple function having an input called a seed
- The function uses the seed in a formula that generates the random value
- Subsequent random values are generated using each previously generated random value as their seeds
- The following code snippet shows a sample call to Random32
RandomRange
- The RandomRange procedure produces a random integer within the range of 0 to n - 1, where n is an input parameter passed in the EAX register
- The random integer is returned in EAX
Randomize
- The Randomize procedure initializes the starting seed value of the Random32 and RandomRange procedures
- The seed equals the time of day, accurate to 1/100 of a second
- Each time you run a program that calls Random32 and RandomRange, the generated sequence of random numbers will be unique
- You need only to call Randomize once at the beginning of a program
- The following example produces 10 random integers
DumpMem
- The DumpMem procedure writes a range of memory to the console window in hexadecimal
- Pass it the starting address in ESI, the number of units in ECX, and the unit size in EBX (1 = byte, 2 = word, 4 = doubleword)
- The following sample call displays an array of 11 doublewords in hexadecimal:
- The following output is produced:
DumpRegs
- The DumpRegs procedure displays the EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP, EIP, and EFL (EFLAGS) registers in hexadecimal
- It also displays the values of the Carry, Sign, Zero, Overflow, Auxiliary Carry, and Parity flags
- Sample output:
- The displayed value of EIP is the offset of the instruction following the call to DumpRegs
ReadKey
- The ReadKey procedure performs a no-wait keyboard check
- In other words, it inspects the keyboard input buffer to see if a key has been pressed by the user
- If no keyboard data is found, the Zero flag is set
- If a keypress is found by ReadKey, the Zero flag is cleared and AL is assigned either zero or an ASCII code
- If AL contains zero, the user may have pressed a special key (function key, arrow key, etc.)
- The AH register contains a virtual scan code, DX contains a virtual key code, and EBX contains the keyboard flag bits
- The upper halves of EAX and EDX are overwritten when ReadKey is called
ReadChar
- The ReadChar procedure reads a single character from the keyboard and returns the character in the AL register
- The character is not echoed in the console window
- If the user presses an extended key such as a function key, arrow key, Ins, or Del, the procedure sets AL to zero, and AH contains a keyboard scan code
- The upper half of EAX is not preserved
ReadString
- The ReadString procedure reads a string from the keyboard, stopping when the user presses the Enter key
- Pass the offset of a buffer in EDX and set ECX to the maximum number of characters the user can enter, plus 1 (to save space for the terminating null byte)
- The procedure returns the count of the number of characters typed by the user in EAX
- ReadString automatically inserts a null terminator in memory at the end of the string
WriteChar
- The WriteChar procedure writes a single character to the console window
- Pass the character (or its ASCII code) in AL
mov al, 'A'
call WriteChar ; displays: "A"
WriteString
- The WriteString procedure writes a null-terminated string to the console window
- Pass the string’s offset in EDX
.data
prompt Byte "Enter your name: ",0
.code
mov edx,OFFSET prompt
call WriteString
Str_length
- The Str_length procedure returns the length of a null-terminated string
- Pass the string’s offset in EDX. The procedure returns the string’s length in EAX
.data
buffer BYTE "abcde",0
bufLength DWORD ?
.code
mov edx,OFFSET buffer ; point to string
call Str_length ; EAX = 5
moc bufLength,eax ; save length
WaitMsg
- The WaitMsg procedure displays the message “Press any key to continue. . .” and waits for the user to press a key
- This procedure is useful when you want to pause the screen display before data scrolls off and disappears
- It has no input parameters
call WaitMsg
WriteWindowsMsg
- The WriteWindowsMsg procedure writes a string containing the most recent error generated by your application to the Console window when executing a call to a system function
call WriteWindowsMsg
ReadDec
- The ReadDec procedure reads a 32-bit unsigned decimal integer from the keyboard and returns the value in EAX. Leading spaces are ignored
- The return value is calculated from all valid digits found until a nondigit character is encountered
- For example, if the user enters 123ABC, the value returned in EAX is 123
- ReadDec affects the Carry flag in the following ways:
- If the integer is blank, EAX = 0 and CF = 1
- If the integer contains only spaces, EAX = 0 and CF = 1
- If the integer is larger than 2321, EAX = 0 and CF = 1
- Otherwise, EAX holds the converted integer and CF = 0