C/C++ 프로그래밍 언어에서 최적화 등 컴파일러의 재량을 제한하는 역할을 한다. 개발자가 설정한 개념을 구현하기 위해 코딩된 프로그램을 온전히 컴파일되도록 한다. 주로 최적화와 관련하여 volatile가 선언된 변수는 최적화에서 제외된다. OS와 연관되어 장치제어를 위한 주소체계에서 지정한 주소를 직접 접근하는 방식을 지정할 수도 있다. Linux Kernel 등의 OS에서 메모리 주소는 논리주소와 물리주소 간의 변환이 이루어진다. 경우에 따라 이런 변환을 제거하는 역할을 한다. 또한 원거리 메모리 점프 기계어 코드 등의 제한을 푼다.
C언어의 경우, 주로 메모리 맵 입출력(MMIO)을 제어할 때, volatile을 선언한 변수를 사용하여 컴파일러의 최적화를 못하게 하는 역할을 한다.
static int foo;
void bar(void) {
foo = 0;
while (foo != 255);
}
foo의 값의 초기값이 0 이후, while 루프 안에서 foo의 값이 변하지 않기 때문에 while의 조건은 항상 true가 나온다. 따라서 컴파일러는 다음과 같이 최적화한다.
void bar_optimized(void){
foo = 0;
while (true);
}
이렇게 되면 while의 무한 루프에 빠지게 된다. 이런 최적화를 방지하기 위해 다음과 같이volatile을 사용한다.
static volatile int foo;
void bar (void) {
foo = 0;
while (foo != 255);
}
이렇게 되면 개발자가 의도한 대로, 그리고 눈에 보이는 대로 기계어 코드가 생성된다. 이 프로그램만으로는 무한루프라고 생각할 수 있지만, 만약 foo가 하드웨어 장치의 레지스터라면 하드웨어에 의해 값이 변할 수 있다. 따라서 하드웨어 값을 폴링(polling)할 때 사용할 수 있다.
※ Memory 참조 버그 (= Buffer Overflow)의 심각성
배열에 할당된 크기 이상의 메모리를 접근할 때 주로 발생한다.
가장 빈번하게 발생하는 보안 취약성의 원인이 된다.
§ 가장 일반적인 형태로는 다음과 같다. - string 입력의 길이를 check하지 않은 경우 - stack에 생성되는 제한된 길이의 문자배열
§ UNIX의 gets 함수 (키보드를 관리해주는 라이브러리)
/* Get string from stdin */
char *gets (char *dest) {
int c = getc();
char *p = dest;
while (c != EOF && c != '\n') {
*p++ = c;
c = getc();
}
*p = '\0';
return dest;
}
cf. 유사한 문제 - strcpy: 임의의 길이의 string을 복사 - scanf, fscanf, sscanf 함수를 %s와 함께 사용하는 경우
§ 위험한 buffer 코드
/* Echo Line */
void echo() {
char buf[4];
gets(buf);
puts(buf);
}
int call_echo () {
printf("Type a string: ");
echo();
return 0;
}
§ stack 상태
§ Buffer Overflow를 피하는 방법
※ string의 길이를 제한하는 라이브러리를 사용하면 된다! ( buffer 주소를 주면서 buffer크기를 check)
▶ gets대신 fgets를 사용 ▶ strcpy대신 strncpy를 사용 ▶scanf를 %s와 함께 사용하지 않는다. ( fgets를 사용한다. )
- Condition code를 setting, 즉 flag register 값을 바꾸는 방법은 다음과 같다.
1. 산술연산 [간접 setting] (단, 이때 leaq명령어로는 값이 바뀌지는 않는다.)
- ex) addq Src, Dest ↔ t = a + b
2. 비교 명령어 [직접 setting]
cmpq Src2, Src1 로 표기하며 이때, Src1 - Src2를 계산하여 비교한다.
(단, Src1 - Src2의 결과를 저장하지는 않는다.)
3. test 명령어 [직접 setting]
testq Src2, Src1 로 표기하며 이때, Src1 & Src2 연산을 진행하여 비교한다.
(단, Src1 & Src2의 결과를 저장하지는 않는다.)
§ Condition Codes값 읽어오기 (feat. SetX 명령)
§ SetX ByteReg
- cmp 명령 실행 후에 적용된다.
- ByteReg의 하위 byte를 condition code (flag register)의 조합 X에 따라 0이나 1로 설정
- 이때, quad와 같은 경우 ByteReg의 나머지 7 byte는 변경이 없다.
즉, %rsp를 예로 들면 최하위 1byte인 %spl을 이용해 0이나 1로 설정한다.
Q. quad를 예로 들면, 그렇다면 1byte를 제외한 나머지 7byte는 어떻게 해야할까? A. movzbl을 이용해 mov를 이용해 copy 후 나머지 나머지 7byte는 0으로 padding 해준다.
Ex. C code -> assembly
int gt (long x, long y) {
return x > y;
}
※ 점프 (jump)
§ jX Label
이때, Label은 assembly에서 위치를 나타내며 특정 label로 jump해서 fetch하게 해주는 것이다.
- 조건코드에 따라서 코드의 실행위치를 이동한다.
§ 조건부 분기예제 (Old Style)
long absdiff (long x, long y) {
long result;
if (x > y)
result = x - y;
else
result = y - x;
return result;
}
§ 조건부 분기예제 (Go to 코드 Style)
§ 조건부 이동명령 (cmovX)
3항 연산자와 같은 한문장으로 표현 가능한 if-else문에서 사용하는데, 위의 go-to보다 더욱 효율적이다.
- 분기문은 pipe라인의 instruction흐름을 매우 방해하지
- 조건부 이동명령은 제어의 이동이 필요가 없기 때문이다.
§ 조건문 컴파일 (Do-While loop)
- 바꾸는 방법: do-while => Go to => assembly
이때,x>>=1의 경우,등호를 통해shift결과를 x에 save할 수 있게 된 것이다.
§ 조건문 컴파일 (While loop)
- 바꾸는 방법:조건문 => do-while => Go to => assembly
§ 일반적인 While문의 번역- 1
§ 일반적인 While문의 번역- 2
§ 조건문 컴파일 (for loop)
- 바꾸는 방법: for => while => do-while => go to => assembly
for (init; test; update)
Body
init;
while (test) {
Body
update;
}
§ 조건문 컴파일 (switch 문)
long switch_eg (long x, long y, long z) {
long w = 1;
switch (x) {
case 1:
w = y * z;
break;
case 2:
w = y / z;
/* Fall through */
case 3:
w += z;
break;
case 5:
case 6:
w -= z;
break;
default:
w = 2;
}
return w;
}
§ jump table 구조
▶ table 구조
- 각 타겟은 8 byte를 필요로 하며 시작주소는 .L4 이다.
▶ jump 하기
▷ 직접 점프: jmp .L8
- 점프 대상은 레이블 .L8로 표시한다.
▷ 간접 점프: jmp *.L4(, %rdi, 8)
- 점프 테이블의 시작: .L4
- 8 byte 주소이기에 8의 배수로 증가해야 함
- 점프 target 유효주소는 .L4 + x * 8 로부터 얻어진다. (0 ≤ x ≤ 6일 때, 성립)