본문 바로가기

Computer System

C<->어셈블리어

어셈블리↔C

C언어 컴파일 과정

전처리기 → 컴파일러 → 어셈블리어 → 링커

test.c → test.exe

전처리기 : test.i

본격적 컴파일 전 준비

외부에 선언된 소스코드, 라이브러리 포함 (include)

매크로 변환(define)

컴파일할 영역 명시

컴파일

전처리가 완료되어도 여전히 소스코드

소스코드를 어셈블리어로 변환

어셈블러

어셈블리어를 기계어(0과1로 표현)로 변환

완료하면 목적 파일(0과1로 이루어짐)이 됨

링킹

각기 다른 목적코드를 하나의 실행파일로 묶어주는 것

컴파일언어

고급언어(소스코드) → 컴파일러 → 저급언어(목적코드)

https://www.youtube.com/watch?v=B8TDaBp3UWo

GCC C 컴파일러는 기계어 코 드를 문자로 표시한 어셈블리 코드의 형태로 출력을 만들어 프로그램의 각 인스트럭션을 만들어 낸다. 그러고 나서, GCC는 어셈블러와 링커를 호출하여 어셈블리 코드로부터 실 행 가능한 기계어 코드를 생성한다

. 커맨드 라인 옵션으로 -0g1을 주면 컴파일러는 본

 C 코드의 전체 구조를 따르는 기계어 코드를 생성하는 최적화 수준을 적용한다.

컴파일러는 두 개의 소스파일의 어셈블리 버전 인 pl.s와 p2.s를 생성한다. 다음으로 어셈블러는 어셈블리 코드를 바이너리 목적코드 인 pl.o와 p2.o로 변환한다. 목적코드는 기계어 코드의 한 유형이다一모든 인스트럭션의 바이너리 표현을 포함하고 있지만 전역 값들의 주소는 아직 채워지지 않았다. 마지막으로 링커는 두 개의 목적코드 파일을 라이브러리 함수들을 구현한 코드와 함께 합쳐서 최종 실행파일인 P를 생성한

  • %rdi: 함수 호출 시 첫 번째 인자(argument)를 전달하는 레지스터입니다. 함수 내에서 인자를 사용할 때 주로 이 레지스터를 참조합니다.
  • %rsi: 함수 호출 시 두 번째 인자를 전달하는 레지스터입니다. 첫 번째 인자와 마찬가지로 함수 내에서 이 레지스터를 통해 두 번째 인자에 접근할 수 있습니다.
  • %rax: 함수의 반환값을 저장하는 레지스터입니다. 함수가 값을 반환할 때 주로 이 레지스터를 사용합니다.

**pushq %rbx**로 스택에 **%rbx**의 값을 저장하고, 함수 종료 시 **popq %rbx**로 스택에서 값을 복원합니다. 이렇게 함으로써 sumstore 함수가 호출되기 전과 호출된 후에 **%rbx**의 값이 변하지 않도록 보존됩니다.

rdx → destination

항상 마지막에는 rax → (%rbx)\

ret = pop rip

rsp 옮기려면 deallocate해줘야함

메모리에서만 *가능

레지스터는 값

CF

unsigned에서 carry out발생 시 1로 표시

뺄셈 후 값이 0보다 작아져도 1로 표시

ZF

연산 값이 0일 때 1로 표기

SF

: 이 플래그는 연산의 결과가 음수일 경우에 '1'로 설정됩니다. SF는 MSB(Most Significant Bit), 즉 가장 높은 비트 값을 복사하여 결정되며 이 비트 값은 보통 부호 비트(sign bit)으로 사용됩니다.

OF

signed 연산에서 오버플로우가 발생하였을 때 1로 표기

Calling incr#1

이 코드는 call_incr 함수를 정의하는 x86-64 아키텍처 어셈블리 코드입니다. 코드를 단계별로 설명하겠습니다:

  1. subq $16, %rsp: 스택 포인터(%rsp)를 16바이트만큼 감소시킵니다. 이는 로컬 변수나 함수 호출 시 필요한 공간을 스택에 할당하기 위한 것입니다.
  2. movq $15213, 8(%rsp): 스택의 8번째 바이트(8(%rsp))에 15213 값을 저장합니다.
  3. movl $3000, %esi: 레지스터 **%esi**에 3000 값을 저장합니다.
  4. leaq 8(%rsp), %rdi: 로컬 변수의 주소(8(%rsp))를 %rdi 레지스터에 로드합니다. 이 주소는 스택의 8번째 바이트로부터 시작합니다.
  5. call incr: incr 함수를 호출합니다. 이 함수는 주어진 주소의 값을 레지스터 **%rsi**에 더하고, 그 값을 다시 해당 주소에 저장한 후, 결과 값을 %rax 레지스터에 반환합니다.
  6. addq 8(%rsp), %rax: 함수 호출 후, 스택의 8번째 바이트에 저장된 값을 %rax 레지스터에 더합니다.
  7. addq $16, %rsp: 스택 포인터를 원래 위치로 되돌립니다.
  8. ret: 함수를 종료하고 호출자로 돌아갑니다.

understanding pointers & Array에서

*A2가 bad인 이유는 reference가 deallocate여서

이게 제일 중요함

이거 매우 중요

ecx → i로 ebp에서 8 떨어진 곳의 값 저장(첫 인자)

ebx → j로 ebp에서 12 떨어진 곳의 값 저장 (두번 째 인자 ) ebx(callee saved)인 이유는 배열의 접근하는 index값으로 바뀌면 안되기 때문이다

edx → i

eax는 9xj → 현재 행까지 도달하기 전 전열의 행개수 X9(column개수)

edx → iX16

eax → 36j 인트가 4니깐 곱하기 4해준거임

edx → 15i

eax → 36j+ iX4 → 전의 행 개수 x9X인트4 + 해당 row에서 몇번째인지 column i X int 4(해당 m2값 저장)

edx → 60i 해당 행까지 도달하기 전 column의개수인 15X4(int)Xi는 해당 row이전의 row 개수들

edx + ebx*4(해당 row에서 몇번째 column인지 Xint 4)에 아까 구한 eax저장

x86 레지스터 정리

EAX: "Accumulator Register"로, 주로 연산의 결과를 저장하는 데 사용됩니다. 함수의 반환 값도 이 레지스터를 통해 전달됩니다.

EBX: "Base Register"로, 데이터 섹션에 대한 포인터로 사용되곤 합니다.

ECX: "Counter Register"로, 반복문의 카운터 등으로 주로 사용됩니다.

EDX: "Data Register"로, EAX와 함께 큰 수의 곱셈이나 나눗셈 등에서 추가적인 데이터를 담는 데 사용됩니다.

ESI와 EDI: 각각 "Source Index"와 "Destination Index"를 의미하며, 문자열이나 배열 등을 복사하거나 이동할 때 주소 값을 가리키는 데 사용됩니다.EBP: "Base Pointer"라고도 하며, 현재 스택 프레임의 베이스(즉 시작점)을 가리키는 용도입니다.ESP: "Stack Pointer"라고도 하며, 현재 스택 프레임의 최상위 부분(top)을 가리킵니다.EIP: "Instruction Pointer", 현재 실행 중인 명령어가 위치한 메모리 주소를 저장합니다.

movl과 leal 차이점은?

  1. movl (Move Long):
    • movl 명령어는 메모리에서 값을 읽어와 레지스터로 옮기는 역할을 합니다.
    • 주로 변수나 메모리 위치의 값을 레지스터로 로드할 때 사용됩니다.
    • 예: **movl (%ecx), %eax**는 메모리 주소 **(%ecx)**에 있는 값을 %eax 레지스터로 복사합니다.
  2. leal (Load Effective Address Long):
    • leal 명령어는 메모리 주소나 주소 계산식을 레지스터로 로드하는 역할을 합니다.
    • 메모리의 값을 읽어오지 않고, 주소나 주소 계산 결과를 레지스터로 옮깁니다.
    • 예: **leal 8(%ecx, %ecx, 4), %edx**는 주소 계산식 **8 + 4 * %ecx**의 결과를 %edx 레지스터로 로드합니다.

차이점은 **movl**은 메모리의 값을 읽어와 레지스터에 저장하는데 사용되고, **leal**은 주소나 주소 계산식의 결과를 레지스터로 저장하는데 사용된다는 것입니다. **leal**은 주소 계산을 위한 명령어이며 메모리에서 값을 읽어오지 않습니다.

이 그림을 보면 이해하기 매우 쉽다.

이 그림을 보면 이 전에 이해가 안됐던

여기도 비교적 쉽게 이해가 간다

4 → 8바이트만큼 할등한다

5 → 여기서 return 시 &로써 주소값을 반환해야하기 때문에 movl이 아닌 leal로 저장 argument에서 레지스터로

6→ eax레지스터 값을 esp에서 4 떨어진 곳에 저장(fun함수 부를 때를 위하여 argument build area)

7—> eax에 x값 저장

8→ x값 esp에 저장 → argument build area

9→ 그리고 fun 함수 호출

fun함수

4→ 2번째 인자의 값을 eax레지스터에 저장. 여기서는 포인터가 *이기에 값을 읽는 것이기 때문에 leal이 아닌 movl로 값을 저장

5- eax에 bp 포인터를 저장

6→ addl을 통해 첫번째 argument x와 bp포인터 더해서 반환

이 과정을 쉽게 설명하면

이렇게 된다.

위 그림에 의해

이 문제는 어떻게 해석될 수 있을까?

4→ 16바이트 할당

5→ 33이라는 값을 ebp에서 아래로 8만큼 떨어진 곳에 저장 a[0] → 위 그림을 보았을 때 local variable 영역에 저장된다고 예측 가능

6→ 이와 마찬가지로 아래로 4만큼 떨어진 곳에 저장 a[1] → local variable 영역에 저장

7→ t가 esp+4였다했는데 16할당 했으니깐 t는 esp에서 20떨어진 곳에 있음 → 이 값을 eax레지스터에 저장

그 후 eax에 a[t] 해당하는 값 넣어줌

포인터차이

*는 값 value를 읽는다 → movl

&는 주소를 가져온다 → 이거 처리하려면 leal

caller saved → edx, eax, ecx . 호출되기 전 값을 저장. 변경 가능함

callee saved → ebx, esi, edi. 함수 내부에서 변경되면 안됨

  • ebx: 배열 인덱스, 일반 데이터 저장 등에 사용됩니다.
  • edx: 곱셈 또는 나눗셈 연산의 중간 결과, 일반 데이터 저장 등에 사용됩니다

 

foo

3→ 스택에서 24바이트 할당

4→ ebp에서 +8인. 즉, first argument를 eax레지스터에 저장

5→ eax레지스터의 값을 스택에 푸시 (argument area에 저장)

6→ ebp에서 4바이트 뺀. 즉 local variable 영역에 buf배열의 주소를 레지스터에 저장

7→계산된 주소를 스택에 푸시 (argument area에 저장 )

8→ strcpy 함수 호출

callfoo

3→ 스택에서 8바이트 공간을 할당 (로컬변수 및 레지스터 저장용ㅇ으로 사용ㅇ )

4→ 스택에서 12바이트만큼 감소 (왜 필요한지 모르겠다고함 )

5→ 문자열 주소를 스택에 푸시한다 (foo의 argument로 넘겨줘야하기 때문에 stack에 푸시된 후 argument build area에 저장된다 )

6→ 그리고 call을 통해 foo 함수를 호출한다

'Computer System' 카테고리의 다른 글

Computer System(chapter 1) review  (0) 2023.04.10