[MASM x86]

 

------------------------------------------------------------------------------------------------------

명령어 push 

push operand (레지스터, 상수)

 

1. esp = esp - 논리주소의 크기 (x86 = 4, x64 = 8)

2. PTR [esp]에 operand 값을 쓴다.  

 

명령어 pop

 

pop operand (레지스터)

1. PTR [operand]의 값을 레지스터에 쓴다.

2. esp = esp + 논리주소의 크기 (x86 = 0x4, x64 = 0x8)

-------------------------------------------------------------------------------------------------------

 

esp에서 시스템 논리주소의 크기를 빼는 이유는 stack이 아래방향으로 자라기 때문이다. 

예를 들어, x86 시스템에서 ESP = 0x0019FE28 EBP = 0x0019FE34 이라고 하면,

 

 push   ebp        //  1.  0x0019FE24 (esp) = 0x0019FE28 (esp) - 0x4

                            2. 메모리[0x0019FE24]에 0x0019FE34를 쓴다.

 

 mov   ebp, esp  //  ebp = 0x0019FE24, esp = 0019FE24


 
pop    ebp       //    1. 메모리 [0x0019FE24] 값을 ebp에 쓴다.

                            2.  0x0019FE28 (esp) = 0019FE24 (esp) + 0x4

 

참고

 

IBM PC 어셈블러 프로그래밍, 이재광 & 전병찬

'프로그래밍 언어 > Assembly' 카테고리의 다른 글

명령어 mov, lea  (0) 2021.03.04
Assembly - 범용 레지스터  (0) 2021.01.17

[MASM x86]

 

ex)

ebp = 0x12345678 

0x12345678번지의 값 = 0x33445566으로 가정하자. 

 

---------------------------------------------------------------------

1. lea eax, ebp

2. lea eax,  DWORD PTR [ebp]

3. mov eax,  ebp

4. mov eax,  DWORD PTR [ebp]

 

1. 은 오류이다. 

2,3은 같은 의미이다. 모두 다 ebp 레지스터의 값(0x12345678)을 eax에 저장한다. 따라서 eax = 0x12345678가 된다. 

4. ebp의 레지스터의 값의 쓰여진 주소로 접근해서 값을 eax에 쓰라는 의미이다. 따라서 eax = 0x33445566가 된다. 

 

2,3이 같다면, mov대신 왜 lea를 쓸까?

찾아보니, 배열에 접근해야 하는 경우 예를 들어, 

 

lea eax,  DWORD PTR [ebp-12]는 성립하지만,

mov eax,  ebp-4는 오류이다. 

 

굳이 mov를 사용하려면,

sub ebp, 12

mov eax, ebp를 해야한다. 만약 수식이 더 복잡해지면 예) ebp - 2*ecx  - 12 

mov를 사용하는 것은 낭비일 것이다. 

하지만, lea를 쓰면 ALU에서 lea 한 명령어로 주소 연산을 완료할 수 있다고 한다. 

 

참고

 

IBM PC 어셈블러 프로그래밍, 이재광 & 전병찬

'프로그래밍 언어 > Assembly' 카테고리의 다른 글

명령어 push, pop  (0) 2021.03.05
Assembly - 범용 레지스터  (0) 2021.01.17

/컴퓨터시스템/Linker 에서 Linker - Windows에서 Static Linking, Dynamic Linking 실습 1,2 를 진행해보았다. 

 

그중에, Linker - Windows에서 Static Linking, Dynamic Linking 실습 2 에서는 명시적 호출을 이용하여 dll을 호출하는  방법을 실습하였다. 

 

Python 인터프리터는 C로 작성되었기에, ctypes 모듈을 호출한다면, 파이썬에서 C/C++ 로 작성된 dll 파일의 함수를 사용할 수 있다.  

 

아래는 지난 실습에서 작성한 DynamicLib_Test.dll에 선언 및 정의된 multiply_four(int num)을 PythonApplication1.py에서 사용하는 예제이다. 

 

DynamicLib_Test (C/C++)

 

<header.h>

extern "C" __declspec(dllexport) int multiply_four(int n);

<src.cpp>

#include "header.h"

extern "C" __declspec(dllexport) int multiply_four(int n)
{
  return (4 * n);
}

하면, DynamicLib_Test.dll이 빌드됨. 

 

PythonApplication1.py (Python 3.9.2)

import ctypes 

dynamic_dll = ctypes.WinDLL('DynamicLib_Test.dll')
myfunc = dynamic_dll['multiply_four']
result = myfunc(100)
print(result)

 

- Linker - Windows 에서 Static Linking, Dynamic Linking 실습 1에서는 묵시적 방법으로 DLL을 호출해보았다. 

 

하지만, 묵시적 방법은 DynamicLib_Test.lib를 사용하며, 실행파일이 로드타임시에 로드되고, 종료시에 해제된다.  

따라서, dynamic 링킹의 의미를 잃지 않나 싶다 (필요할때 쓰고, 필요하지 않으면 쓰지 않는). 

그러므로, 이번에는 명시적 방법으로 DLL을 호출 해보자. 

 

WinDbg로 본 DynamicLib의 묵시적 호출. Execution Time이 아닌 Load Time시에 호출된다.  

Windows API가 제공하는 LoadLibrary(), GetProcAddress(), FreeLibrary()를 이용하여 명시적으로 호출하면, dll을 .lib와 관계 없이 사용가능하며, 로드시간이 아닌 실행시간중 필요할때 호출하고, 사용후 free할 수 있다.  

따라서 암시적 호출에서 사용했던 header파일 include나 .lib파일을 추가 시키지 않아도 된다.

다만, dll파일을 실행파일과 같은 디렉토리에 두는것은 변함이 없다. 

 

1. 프로젝트 속성 > C/C++ > 일반 > 추가 포함 디렉터리 > 에 "dynamic_source.h"의 디렉토리 경로 추가.  (X)

2. 프로젝트 속성 > 링커 > 일반 > 추가 라이브러리 디렉터리 > 에 "DynamicLib_Test.lib"의 디렉토리 경로 추가. (X)

3. 프로젝트 속성 > 링커 > 입력 > 추가 종속성 > DynamicLib_Test.lib 추가 (X)

4. MyApp.exe이 들어있는 폴더 안에 DynamicLib_Test.dll 가져다 놓기 (0) 

 

아래는 명시적 호출의 예시 코드 이다. 

 

#include <iostream>
#include <Windows.h>

typedef int (*MY_FUNCTION) (int);

int main() 
{
  HMODULE hDll = LoadLibrary(L"DynamicLib_Test.dll");

  if (hDll != NULL)
  {
     MY_FUNCTION my_function = (MY_FUNCTION)::GetProcAddress(hDll, "multiply_four");
     std::cout << "result : " << my_function(100) << std::endl;
     FreeLibrary(hDll);
  }
}

결과는 실습1과 마찬가지로 400이 잘 나온다.

 

WinDbg로 본 DynamicLib의 명시적 호출.  Load Time이 아닌 Execution Time에 호출됨.

 

Windows 운영체제에서 *.lib는 정적 라이브러리, *.dll은 동적 라이브러리 파일이다. 

MSVC 2019로 실습을 해보자. 우선 라이브러리를 갖다가 쓸 콘솔 앱을 하나 생성한다. 

 

 

"MyApp.cpp"

#include <iostream>

int multiply_two(int n)
{
  return (2 * n);
}

int main()
{
  std::cout << multiply_two(100) << std::endl; // 200
}

위와 같이 int형 인자 한 개를 받아서 2를 곱해 반환하는 multiply_two() 함수를 구현해보기로 한다. 

 

<정적 라이브러리 생성>

 

"static_source.h"

int multiply_two(int n);

"static_source.cpp"

#include "static_source.h"

int multiply_two(int n)
{
  return (2 * n);
}

빌드 하면 아래와 같이 "StaticLib_Test.lib" 파일이 생성된다. 

다시 "MyApp.cpp"로 넘어가서, "StaticLib_Test.lib"를 정적 라이브러리로 링크하여 사용할 수 있게 하자. 

프로젝트 속성 : "모든 구성" 플랫폼 : "모든 플랫폼"을 선택 한다. 

 

1. 프로젝트 속성 > C/C++ > 일반 > 추가 포함 디렉터리 > 에 "static_source.h"의 디렉토리 경로 추가. 

2. 프로젝트 속성 > 링커 > 일반 > 추가 라이브러리 디렉터리 > 에 "StaticLib_Test.lib"의 디렉토리 경로 추가. 

3. 프로젝트 속성 > 링커 > 입력 > 추가 종속성 > StaticLib_Test.lib 추가

 

#include <iostream>
#include "static_source.h"

int main()
{
  std::cout << multiply_two(100) << std::endl; // 200
}

 

 

<동적 라이브러리 생성>

 

- 묵시적, 명시적 방법이 있다

- (묵시적인 방법이 명시적인 방법보다 사용하기 쉽지만, 빌드시에 .lib이 개입하며, 로드 타임에 강제로 연결되고 프로그램이 종료될때 까지 달고 있어야 하므로 dynamic linking의 원칙에 조금 맞지 않다고 생각한다).

- 다만, 이번 실습에서는 묵시적 방법을, 다음 실습에서는 명시적 방법을 사용해보자.  

- 4를 곱해주는 기능을 구현한다. 

 

"dynamic_source.h"

extern "C" __declspec(dllexport)int muliply_four(int n);

"dynamic_source.cpp"

#include "dynamic_source.h"

extern "C" __declspec(dllexport)int muliply_four(int n)
{
  return (4 * n);
}

빌드 하면, 아래와 같이 "DynamicLib_Test.lib", "DynamicLib_Test.dll"이 생성되었다. 

 

 

다시 "MyApp.cpp"로 넘어가서, 마찬가지로 "DynamicLib_Test.dll"를 동적 라이브러리로 링크하여 사용할 수 있게 하자. 

프로젝트 속성 : "모든 구성" 플랫폼 : "모든 플랫폼"을 선택 한다. 

 

1. 프로젝트 속성 > C/C++ > 일반 > 추가 포함 디렉터리 > 에 "dynamic_source.h"의 디렉토리 경로 추가. 

2. 프로젝트 속성 > 링커 > 일반 > 추가 라이브러리 디렉터리 > 에 "DynamicLib_Test.lib"의 디렉토리 경로 추가. 

3. 프로젝트 속성 > 링커 > 입력 > 추가 종속성 > DynamicLib_Test.lib 추가

4. MyApp.exe이 들어있는 폴더 안에 DynamicLib_Test.dll 가져다 놓기 

 

 

목적파일 이란

 

- Code (.c, .cpp, .h)-> ASCII File (.i) -> 어셈블리 파일 (.asm) -> assembler에 의해 assemble된 바이트 블록 집합 파일이다.

 

목적파일의 종류 

 

적색 :  재배치 가능 목적파일

파란색 : 실행 가능 목적파일

녹색 : 공유 목적 파일

 

 

1. 재배치 가능 목적파일 (Relocatable object file)

- 아래에 있는 dog.o, cat.o, main.o 같이 컴파일되고 어셈블 처리된 목적 파일이다.

- 다른 목적파일과 결합될 수 있도록 바이너리 코드와 데이터가 있다. 

- 심볼 (모듈에서 정의하거나 다른 모듈에서 사용할)이 존재한다. 이는 곧, C언어에서 static, extern 키워드와 관련됨.

- 모듈당 단 하나의 심볼 정의를 허락한다 (중복 x).

- 해당 모듈에서 찾을 수 없는 심볼을 발견하면, 다른 모듈에 정의되어 있을것이라고 간주하고 링커에게 남겨둔다.

- 링커는 심볼해석 -> 심볼 참조의 재배치 (PC-상대 참조의 재배치, 절대 참조의 재배치)로 이루어진다.

 

ELF Header ELF Header의 크기, 목적파일 타입 (재배치 가능, 실행가능, 공유), 머신타입, 섹션 헤더 테이블 오프셋, 섹션헤더 테이블의 크기, 엔트리 수
.text 컴파일된 프로그램 머신코드
.rodata 포맷 스트링, 읽기-허용 데이터
.data 초기화된 전역 변수, 정적 변수
.bss 초기화 되지 않은 전역 변수, 정적 변수
.symtab 전역변수와 함수에 대한 심볼테이블 (지역변수 엔트리 없음)
.rel .text 다른 목적파일과 연결될때 수정되는 .text 섹션내의 위치들의 리스트. 
.red .data 전역변수들에 대한 재배치 정보
.debug 지역변수, typdef, 전역변수들을 위한 디버깅 심볼 테이블
.line .text 섹션내의 머신코드 인스트럭션 내 라인들간의 매핑
.strtab 섹션내의 심볼테이블과 섹션헤더들에 있는 섹션을 위한 스트링 테이블
Section Header Table 위에 서술된 모든 섹션들에 대한 정보를 기술함

                                                        <테이블 1> 재배치 가능 목적파일의 구조

 

2. 실행 가능 목적파일 (Executable object file)

 

- 재배치가 완료된 목적파일 (.rel이 더 이상 필요하지 않다)

- 예를 들어 리눅스 상에서 ./${프로그램이름}과 같이 실행되면, execve같은 함수로 로더를 호출하여 프로그램을 실행함. 

- 실행 가능 목적파일의 (.data, .bss, .init, .text, .rodata)가 메모리로 복사된다.

- 로더는 엔트리포인트 _start 함수의 주소로 점프 -> _libc_start_  main 호출 -> 실행환경 초기화 -> 사용자 main 함수 호출 한다.

ELF Header ELF Header의 크기, 목적파일 타입 (재배치 가능, 실행가능, 공유), 머신타입, 섹션 헤더 테이블 오프셋, 섹션헤더 테이블의 크기, 엔트리 수 (Read-Only)
Segment Header Table ELF Header와 함께 연속적인 파일 섹션을 런타임 메모리 세그먼트와 매핑함 (Read-Only)
.init 프로그램 초기화 코드에서 호출함 (Read-Only)
.text 컴파일된 프로그램 머신코드 (Read-Only)
.rodata 포맷 스트링, 읽기-허용 데이터 (Read-Only)
.data 초기화된 전역 변수, 정적 변수 (Read & Write)
.bss 초기화 되지 않은 전역 변수, 정적 변수 (Read & Write)
.symtab 전역변수와 함수에 대한 심볼테이블 (지역변수 엔트리 없음, Not loaded into memory)
.debug 지역변수, typdef, 전역변수들을 위한 디버깅 심볼 테이블 (Not loaded into memory)
.line .text 섹션내의 머신코드 인스트럭션 내 라인들간의 매핑 (Not loaded into memory)
.strtab 섹션내의 심볼테이블과 섹션헤더들에 있는 섹션을 위한 스트링 테이블 (Not loaded into memory)
Section Header Table 위에 서술된 모든 섹션들에 대한 정보를 기술함 (Not loaded into memory)

 

3. 공유 목적 파일 (Shared object file) 

 

응용프로그램이 로딩되기전에 혹은 실행되는 중에 로드하고 링킹될 수 있다. 

 

Randal E Bryant, David R O'Hallaron <Computer Systems : A Programmer's Perspective> 3rd edition

 

<네트워크>

 

호스트

 

- 네트워크 어댑터는 I/O 디바이스중의 하나이다. 

- DMA (CPU 개입없이 IO 디바이스와 메모리간의 데이터를 송수신 하는 방식)

- 호스트는 프레임(헤더에 출발지, 목적지, 프레임 길이 정보등이 담겨 있고 그 뒤에 실제 내용이 담겨 있음)을 통해 주고 받는다. 

 

하위 수준의 네트워크 : LAN

 

Ethernet Segment

- 한 포트에서 수신한 비트를 다른 포트로 모두에게 복사함.

- 방이나 층단위의 작은 지역에 설치함. 

Bridge Ethernet

- 이더넷 세그먼트가 연결되어 구성되는 대규모 LAN임

- 전체 빌딩이나 캠퍼스 규모

- 허브보다 높은 대역폭을 씀

- 분산 알고리즘을 사용하여 선택적으로 프레임을 복사함

 

<클라이언트 - 서버 모델>

 

- {서버, 클라이언트} / {호스트, 머신}와 다르다. // 한 개의 호스트는 여러개의 서버, 클라이언트를 실행시킬 수 있음. 

- 기본적인 연산의 단위는 트랜잭션임 (데이터베이스에서 쓰이는 트랜잭션과 다름).

 

클라이언트 : 한 개 이상의 프로세스로 구성됨. 서버에게 request를 보냄. 

서버 : 한 개의 프로세스로 구성됨. 클라이언트로 부터 요청을 받고, 요청을 처리하여 클라이언트에게 응답함. 

 

참고

 

Randal E Bryant, David R O'Hallaron <Computer Systems : A Programmer's Perspective> 3rd edition

static이 내부 연결 (internal linkage)를 수행한다면, extern은 외부연결(external linkage)를 수행함으로써, 파일을 넘어서 전체 프로그램에서 유효하도록 하기 위함이다.

 

사용 방법은 하나의 변수당 한 번만 선언되어야 하고, 한 번만 정의 될수 있다. 

아래는 사용 예시이다. "myvar.h"에서 extern 키워드가 없으면 링킹이 되지 않아서 빌드 에러가 발생한다. 

 

"var.h"

extern int myvar; // extern variable declaration

"var.cpp"

#include "var.h"

int myvar = 0; // extern variable definition

"dog.h"

void do_dog();

"dog.cpp"

#include <iostream>
#include "var.h"
#include "dog.h"

void do_dog()
{
   myvar += 1000;
   std::cout << myvar << std::endl;
}

"cat.h"

void do_cat();

"cat.cpp"

#include <iostream>
#include "var.h"
#include "cat.h"

void do_cat()
{
   myvar += 10;
   std::cout << myvar << std::endl;
}

"main.cpp"

#include "dog.h"
#include "cat.h"

int main()
{
   do_cat(); // 10
   do_dog(); // 10 + 1000 = 1010
}

 

참고

 

Brian Kernighan & Dennis Ritchie, <The C Programming Language> 2nd edition

Bjarne Stroustrup, <The C++ Programming Language> 4th edition

'프로그래밍 언어 > C' 카테고리의 다른 글

C - 키워드 static  (0) 2021.01.24

+ Recent posts