Hello World는 분석하기에 너무 명령어 수도 적고 실력도 크게는 늘지 않을 것 같아 조금 더 소스를 늘려 본 코드를 가져왔다.

조금이지만 더 복잡하므로 분석할 가치는 있어 보인다.




argv[1]로 문자열을 입력받고, 총 10번 해당 문자열을 출력해 주는 간단한 프로그램이다.

이를 컴파일하고 디스어셈블해서 보면 다음과 같다.


대충 봐도 Hello World보다는 훨씬 길고 복잡한 코드이다.

이도 마찬가지로 쭉 분석해 보겠다.





   0x00010480 <+0>: push {r11, lr}

// lr과 r11을 스택에 저장한다. 전에도 봤다시피 함수 프롤로그인 것 같다.

   0x00010484 <+4>: add r11, sp, #4

// r11에 sp+4의 값을 구하여 저장한다.

   0x00010488 <+8>: sub sp, sp, #16

// sp에서 16을 빼서 다시 sp에 저장한다. 스택에 공간을 할당하는 부분이다.

   0x0001048c <+12>: str r0, [r11, #-16]

// *(r11-16)에 r0을 집어넣는다.

   0x00010490 <+16>: str r1, [r11, #-20]

// *(r11-20)에 r1을 집어넣는다.

   0x00010494 <+20>: ldr r3, [r11, #-16]

// r3에 *(r11-16)의 값을 가져와 넣는다.

// 위에서 *(r11-16)에 r0을 넣었으므로 이 명령 이후 r3에는 r0의 값이 들어 있다.

   0x00010498 <+24>: cmp r3, #2

// r3와 상수 2를 비교한다.

   0x0001049c <+28>: beq 0x104b0 <main+48>

// 만약 같았다면 main+48인 0x104b0으로 점프한다.

   0x000104a0 <+32>: ldr r0, [pc, #136] ; 0x10530 <main+176>

// 다르다면 *(pc+136)의 값을 r0에 집어넣는다.

   0x000104a4 <+36>: bl 0x1031c

// 0x1031c로 점프한다.

// 실행시켜본 결과 printf함수가 실행되었다. r0에 들어간 것은 문자열의 주소였을 것이다.

   0x000104a8 <+40>: mov r3, #1

// r3에 1을 집어넣고

   0x000104ac <+44>: b 0x10524 <main+164>

// main+164로 점프하는데, 이곳은 확인해보면 함수 에필로그 부분이다.

// 즉, 위 조건문에서 argc를 비교하고 2가 아닐 경우 printf 함수 실행 후 return 1을 수행한다는 것을 알 수 있다.


   0x000104b0 <+48>: ldr r3, [r11, #-20]

// 정상적으로 조건문을 통과하면 여기로 오게 된다.

// 이번엔 r3에 *(r11-20)을 집어넣는다.

   0x000104b4 <+52>: add r3, r3, #4

// r3에 4를 더한다.

   0x000104b8 <+56>: ldr r3, [r3]

// r3안의 값을 가져와서 r3에 넣는다.

// argv에서 argv[1]의 값을 가져오는 과정이었다고 생각된다.

   0x000104bc <+60>: mov r0, r3

// r0에 r3를 넣는다. ARM에서는 함수 인자를 r0에 넣는 모양이다.

   0x000104c0 <+64>: bl 0x10340

// 이후 0x10340의 함수를 호출한다.

   0x000104c4 <+68>: mov r3, r0

// r0을 r3에 집어넣는다.

   0x000104c8 <+72>: cmp r3, #256 ; 0x100

// r3와 0x100을 비교한다. 방금 점프한 곳은 strlen이었던 것 같다.

   0x000104cc <+76>: bls 0x104e0 <main+96>

// bls 명령은 비교 결과가 작았을 경우 점프하는 명령이다.

   0x000104d0 <+80>: ldr r0, [pc, #92] ; 0x10534 <main+180>

// 256보다 컸을 경우에는 점프하지 않으므로 다음 명령이 바로 실행되는데,

// r0에 *(pc+92)를 집어넣는다.

   0x000104d4 <+84>: bl 0x1031c

// 위에서 확인한 printf 함수의 주소이다.

   0x000104d8 <+88>: mov r3, #1

   0x000104dc <+92>: b 0x10524 <main+164>

// 마찬가지로 r3에 1을 넣고 main의 에필로그로 점프한다.


   0x000104e0 <+96>: mov r3, #0

// 두 번째 조건문까지 무사히 통과하면 r3에 0을 넣는다.

   0x000104e4 <+100>: str r3, [r11, #-8] 

// *(r11-8)에 r3를 집어넣는다.

   0x000104e8 <+104>: b 0x10514 <main+148>

// main+148로 점프한다. 반복문의 시작일 듯 하다. 바로 main+148로 점프하자.


   0x000104ec <+108>: ldr r3, [r11, #-20]

// r3에 *(r11-20)의 값을 가져와 저장한다.

   0x000104f0 <+112>: add r3, r3, #4

// r3에 4를 더하고,

   0x000104f4 <+116>: ldr r3, [r3]

// *r3를 가져와 r3에 다시 넣는다.

// 위에서 봤던 구조와 비슷한 게 argv[1]을 가져오는 과정이다.

   0x000104f8 <+120>: ldr r0, [pc, #56] ; 0x10538 <main+184>

// *(pc+56)을 r0에 집어넣는다.

// 이 값을 확인해 보면 아마 포맷스트링이 나올 것이다.

   0x000104fc <+124>: ldr r1, [r11, #-8]

// r1에는 *(r11-8)을 집어넣는다. 카운터로 사용하는 값이다.

   0x00010500 <+128>: mov r2, r3

// r3를 r2로 집어넣는데, 여기에는 출력할 문자열의 주소가 들어 있다.

// 여기까지 레지스터의 상태를 확인해 보면, r0에는 *(pc+56)으로 포맷스트링이,

// r1에는 *(r11-8)로 카운터 변수, r2에는 argv[1]이 들어 있다.

   0x00010504 <+132>: bl 0x10310

// 뭔가 함수를 실행하는데, 아마 printf일 것 같다. 왜 위의 printf와 다른 주소인지는 잘 모르겠지만 아마 인자의 차이가 아닐까 싶다.

   0x00010508 <+136>: ldr r3, [r11, #-8]

// r3에 *(r11-8)을 가져와 집어넣는다.

   0x0001050c <+140>: add r3, r3, #1

// r3에 1을 더해서 다시 저장하고,

   0x00010510 <+144>: str r3, [r11, #-8]

// 이를 다시 *(r11-8)에 저장한다. 카운터 변수에 ++연산을 해준 모습이다.

   0x00010514 <+148>: ldr r3, [r11, #-8]

// r3에 *(r11-8)을 집어넣는다.

// 방금 반복문의 카운터가 되는 모양으로, 처음에는 0으로 초기화시켰다.

   0x00010518 <+152>: cmp r3, #9

// r3와 9를 비교하고,

   0x0001051c <+156>: ble 0x104ec <main+108>

// 같거나 작을 경우에는 main+108로 점프한다.


// for문 내부를 확인해 보면 총 10번 돌면서 계속 카운터 변수와 argv[1]를 출력함을 알 수 있다.

   0x00010520 <+160>: mov r3, #0

// r3에 0을 집어넣는다.

   0x00010524 <+164>: mov r0, r3

// r0에 r3의 값을 넣는다.

   0x00010528 <+168>: sub sp, r11, #4

// sp에 r11-4를 집어넣는다.

// 지금 보니 함수의 첫부분에 r11에 sp+4를 저장했으므로 이는 r11을 Stack Base Pointer로 쓰는 것과 비슷하다.

// 마지막에 r11-4를 sp에 넣는 것으로 복구까지 시켜주고, 변수를 이 기준으로 참조까지 하는 것을 보니 확실해 보인다.

   0x0001052c <+172>: pop {r11, pc}

// pc와 r11에 각각 pop 하여 함수를 끝낸다.

// 이 이후 명령은 뭔지 잘 모르겠다.

   0x00010530 <+176>: ; <UNDEFINED> instruction: 0x000105b0

   0x00010534 <+180>: andeq r0, r1, r4, asr #11

   0x00010538 <+184>: ldrdeq r0, [r1], -r8



생각했던것보다 양이 많아서 분석하는 데에 오래 걸렸다.

특히 처음에 r11에 sp+4를 집어넣는데 그 이유를 몰라서 더 힘들었던 것 같다.

왜 하필 sp+4를 해서 넣어주는지는 잘 모르겠지만 이게 Base Pointer 역할을 하고 있다는 것을 알게 되었다.

이번에도 간단한 명령들 뿐이긴 했지만 막상 이렇게 분석을 해 보니 역시 ARM은 명령어 체계를 압축해서 쓰며

따라서 원래 Intel계열 어셈블리보다 오히려 더 분석이 쉽고 명확한 부분도 있는 것 같았다.

나름 ARM도 해볼 만한 분야인 것 같다. 재미있다.

블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

댓글을 달아 주세요