둘 다 “이 변수가 메모리의 어디에 있는가”를 레지스터에 넣을 때 쓴다. 가르는 질문은 “그 주소를 프로그램을 돌리기 전에 알 수 있는가”다. 알 수 있으면 OFFSET, 돌려 봐야 알면 LEA다.
OFFSET은 어셈블러가 번역할 때 주소를 계산해 상수로 박아 넣는 연산자다. 실행될 때는 이미 숫자다. 그래서 번역할 때 자리가 정해진 것(.data의 변수)에만 쓸 수 있다.LEA는 실행될 때 CPU가 주소를 계산하는 명령이다.[ebp-8]처럼 레지스터 값에 따라 달라지는 주소도 구한다.
스택 매개변수와 지역 변수는 EBP에서의 거리로만 정해진다. EBP는 프로시저가 불릴 때마다 다를 수 있다. 그래서 이 둘의 주소를 얻으려면 LEA를 써야 한다.
CopyString PROC,
count:DWORD
LOCAL temp[20]:BYTE
mov edi,OFFSET count ; 오류: 스택 매개변수
mov esi,OFFSET temp ; 오류: 지역 변수
lea edi,count ; 된다
lea esi,temp ; 된다
이름을 붙이지 않고 [ebp-8]에 있는 지역 변수라면 이렇게 쓴다.
mov esi,OFFSET [ebp-8] ; 오류
lea esi,[ebp-8] ; ESI = EBP - 8
[ebp-8]의 주소가 바깥 호출에서는 00000FF0h, 그 안에서 불린 호출에서는 00000FE0h로 다르다. 스택이 다른 곳에서 시작하면 또 달라진다. 전역 변수의 OFFSET은 그대로다. lea는 주소를, mov는 그 자리의 값을 넣는다 — 45_offset-vs-lea_verify.py (실제 어셈블러가 아니라 파이썬 흉내로 확인)어느 쪽일까
C1 (a) .data에 msg BYTE "Hello",0이 있다. 이 문자열의 주소를 EDX에 넣는다. (b) 프로시저 안에서 LOCAL buf[20]:BYTE로 만든 배열의 주소를 ESI에 넣는다. 각각 어떻게 쓰는가?
답: (a) mov edx,OFFSET msg. (b) lea esi,buf.
이유: msg의 자리는 번역할 때 정해진다. buf의 자리는 이 프로시저가 불린 순간의 EBP에서 20바이트 아래이고, EBP는 실행해 봐야 안다. (a)를 lea edx,msg로 써도 같은 주소가 들어간다1.
C2 (c) 스택 매개변수 count:DWORD의 값을 ECX에 넣는다. (d) 같은 매개변수의 주소를 EDI에 넣는다. 어느 쪽에 LEA가 필요한가?
답: (c)는 주소가 아니라 값이 필요하므로 mov ecx,count(곧 mov ecx,[ebp+8])다. (d)는 lea edi,count다. mov edi,OFFSET count는 오류다.
이유: MOV는 그 자리에 든 값을 읽고, LEA는 그 자리가 어디인지만 계산한다. [ebp-8]에 55h가 들어 있고 그 주소가 0FF4h일 때 mov는 55h를, lea는 0FF4h를 넣는다1.
결정적 차이
OFFSET |
LEA |
|
|---|---|---|
| 정체 | 연산자 (어셈블러가 번역할 때 계산) | 명령 (CPU가 실행할 때 계산) |
| 결과 | 상수 | 그 순간의 레지스터 값으로 계산한 주소 |
전역 변수(.data) |
된다 | 된다 |
| 스택 매개변수, 지역 변수 | 안 된다 | 된다 |
[ebp-8], [esi+4] 같은 식 |
안 된다 | 된다 |
| 쓰는 꼴 | mov esi,OFFSET 이름 |
lea esi,피연산자 |
선택을 가르는 줄은 “결과”다. 지역 변수의 주소가 상수일 수 없다는 것은 프로시저를 두 깊이에서 불러 보면 드러난다. 바깥 프로시저와 그 안에서 불린 프로시저가 똑같이 sub esp,8 뒤 lea reg,[ebp-8]을 하면, 바깥은 00000FF0h를, 안쪽은 00000FE0h를 얻는다. 식은 같은데 값이 다르다. 번역할 때 하나의 숫자로 정해 둘 수가 없다1.
둘 다 아닐 때
C3 주소를 구하는 것처럼 보이지만 OFFSET도 LEA도 직접 쓰지 않는 경우 둘을 쓰라.
답: ① INVOKE로 프로시저를 부르면서 변수의 주소를 인수로 넘길 때는 ADDR을 쓴다(INVOKE Sub2,ADDR byteVal). ② 주소가 아니라 그 자리의 값이 필요하면 MOV를 쓴다. 이미 주소가 레지스터에 들어 있으면 [esi]로 따라가 읽는다.
연결
OFFSET의 뜻과 전역 변수에서의 쓰임: OFFSET과 PTR 연산자- 번역할 때 처리되는 것과 실행할 때 처리되는 것의 구분: 지시어와 명령어 비교
LEA가 필요한 대상: 지역 변수, 스택 매개변수ADDR: INVOKE·PROC·PROTO 지시어