ARM Assembly에 대해 공부하던 중 좋은 글이 있어 번역 하였다.

출처: Azeria Labs (https://azeria-labs.com/writing-arm-assembly-part-3/)


ARM은 실행하기 위해 두 개의 상태(Jazelle는 제외하도록 합니다)를 가지고 있습니다 - ARM과 Thumb 입니다. 이 상태들은 권한과는 관계가 없습니다. 예를들어 SVC 모드에서 실행되는 코드는 ARM 일 수도 Thumb일 수도 있습니다. 두 상태의 주요 다른 점은 명령어 셋입니다. ARM 상태일때는 명령어가 항상 32-bit 이지만, Thumb 상태의 명령어 셋은 16-bit 입니다(그러나 32-bit 일 수도 있습니다). 언제, 어떻게 Thumb를 쓰는지 아는 것이 ARM 익스플로잇 개발에 매우 중요합니다. ARM 쉘코드를 작성할 때 우리는 16-bit Thumb 명령어를 통해 NULL 바이트를 없앨 수 있습니다.

ARM의 호출 규약은 헷갈리는 것 이상이며, 모든 ARM 버전이 같은 Thumb 명령어 셋을 지원하지 않습니다. 어떤 부분에서는, ARM이 향상된 Thumb 명령어 세트(이름: Thumbv2)를 지원하며, 해당 명령어 셋은 32-bit Thumb 명령어 셋과 이전 버전에서 제공하지 않은 조건문 분기를 지원합니다. Thumb 상태에서 조건문 실행을 하기 위해서 it 명령어가 새로 추가되었습니다. 그렇지만, 이 명령이 이후 버전에서 삭제되면서 덜 복잡한 명령으로 변경되었습니다. ARM/Thumb 명령어 셋의 모든 변경사항을 알지는 못하지만, 솔직하게는 신경 쓰지 않습니다. 당신도 마찬가지 입니다. 너가 알아야 될 것은 당신이 목표로 하는 기기의 ARM 버전과 Thumb 서포트 여부를 통해 당신의 코드를 수정하는 것입니다. ARM Infocenter는 어떻게 하면 당신의 ARM 버전을 알 수 있는지 가이드를 제공하고 있습니다.

위에서 이미 언급 했듯이, Thumb 버전은 여러개가 있습니다. 이름이 다른것은 각각의 차별점을 두기 위함입니다 (프로세서 자체는 언제나 Thumb로 불립니다.)

  • Thumb-1 (16-bit 명령어): ARMv6 및 이전 아키텍쳐에서 사용
  • Thumb-2 (16-bit and 32-bit 명령어) Thumb-1을 확장하여 더 많은 명령어를 16-bit 혹은 32-bit 로 사용할 수 있게 함 (ARMv6T2, ARMv7)
  • ThumbEE: 동적 생성 코드에 대응하기 위한 변경 및 추가 버전 (코드가 기기에 명령 실행 직전 혹은 동안에 컴파일됨)

ARM과 Thumb의 차이:

  • 조건부 실행: 모든 ARM 명령어는 조건부 실행을 지원합니다. 일부 ARM 프로세서 버전은 Thumb에서도 조건부 실행을 IT 명령을 통해 지원합니다. 조건부 실행은 실행하는 명령어의 숫자를 줄이고 많은 자원이 드는 분기 명령을 줄임으로서 코드의 밀도를 높여줍니다.
  • 32-bit ARM과 Thumb 명령어: 32-bit Thumb 명령은 명령어 뒤에 w 가 붙습니다.
  • 배럴 시프터(barrel shifter)는 ARM 모드의 특이한 기능입니다. 이것은 여러 명령을 1개로 줄여줍니다. 예를 들어서 곱하기 명령 실행을 위해 레지스터를 2로 곱한 후 MOV로 결과를 저장하는 두 개의 명령어를 MOV 명령만 실행하고 MOV 내부에서 오른쪽으로 1 시프트 하여 곱할 수 있도록(MOV R1, R0, LSL#1; R1 = R0 * 2) 할 수 있습니다.

프로세서가 실행하는 상태 변경을 위해서는, 아래 둘 중 하나의 조건을 갖추어야 합니다.

  • 분기 명령을 BX(분기 및 교환) 혹은 BLX(분기, 링크 그리고 교환) 명령을 사용할 수 있어야 하고 목적 레지스터의 가장 낮은 바이트(least-signifant-bit)가 1이여야 합니다. 오프셋에 1을 더하면 이 기준을 충족할 수 있습니다 (예: 0x5530 + 1). 이렇게 하면 비트 정렬(alignment) 문제가 생길 수 있다고 생각할 수 있지만, 명령어들이 이미 2바이트 혹은 4바이트로 정렬돼 있고 가장 낮은 바이트(least-significant-bit)는 프로세서가 무시하므로 문제가 되지 않습니다. 더욱 자세한 것은 Part 6. 조건 실행과 분기 를 통해 확인하세요.
  • 우리는 CPSR에 T 비트가 설정돼 있으면 Thumb 모드임을 알 수 있습니다.

ARM 명령어 셋 소개

이 파트를 통해 ARM의 명령어 세트를 소개합니다. 이 파트를 통해서 어셈블리가 얼마나 작은 단위의 명령을 수행하는지 알아보고, 어떻게 그 명령어들이 연결되어 있고, 명령어들을 엮어서 사용하는 방법에 대해서 배웁니다.

위에서 언급했듯이, 어셈블리 언어는 메인 블록을 만들기 위한 명령어 셋의 집합으로 이루어져 있습니다. ARM 명령어는 한개 혹은 두개의 피연산자를 아래 템플릿 형태로 가지고 있습니다.

MNEMONIC{S}{condition} {Rd}, Operand1, Operand2

ARM 명령어 세트의 유연성으로 인해서, 필드 안의 모든 인자들이 사용되지는 않습니다. 그렇지만 템플릿은 아래와 같은 내용으로 각 필드를 정의하고 있습니다.

MNEMONIC - 명령어의 별칭
{S} - (선택) 명령어 접미사. S가 기재되어 있다면 명령의 결과에 따라 조건 플래그가 업데이트 됩니다
{condition} - 명령어 실행을 위해 맞추어야 하는 조건
{Rd} - 명령 실행 결과를 저장할 레지스터 (도착지)
Operand1 - 첫번째 인자값. 레지스터 혹은 값 직접 입력
Operand2 - 두번째 인자값(일수도 있고 아닐수도 있음). 직접 값 입력 혹은 레지스터

위의 값들 중 MNEMONIC, S, Rd, Operand1 는 고정돼 있지만 condition과 Operand2의 경우 상황에 따라 다르게 쓰일 수 있습니다. condition 필드가 CPSR 레지스터의 값을 기반으로 설정이 되기 때문입니다. Operand2는 유연한 인자값으로 불리는데, 우리가 이 값을 다양하게 쓸 수 있기 때문입니다. 직접 값으로 사용할 수도 있고, 레지스터나, 쉬프트 한 레지스터 값 등을 포함할 수 있습니다. 예를 들어 우리는 Operand2에 아래와 같은 표현을 사용할 수 있습니다.

#123 - 값 직접 입력
Rx - Register x (R1, R2, R3, ...)
Rx, ASR n - 레지스터 x를 오른쪽으로 n비트 만큼 산술 쉬프트 한 값
Rx, LSL n - 레지스터 x를 왼쪽으로 n비트 만큼 논리 쉬프트 한 값
Rx, LSR n - 레지스터 x를 오른쪽으로 n비트 만큼 논리 쉬프트 한 값
Rx, ROR n - 레지스터 x를 n비트 만큼 로테이션 한 값
Rx, RRX - 레지스터 x를 1비트만큼 확장하여 로테이션 한 값

단적으로 명령어들이 어떻게 다르게 생겼는지 아래의 예시를 통해 알아봅시다.

ADD R0, R1, R2 - R1과 R2를 더해서 R0에 저장
ADD R0, R1, #2 - R1과 2를 더해서 R0에 저장
MOVLE R0, #5 - 조건 LE(적거나 같음)이 충족하면 숫자 5를 R0으로 옮깁니다
MOV R0, R1, LSL #1 - R1을 오른쪽으로 1비트 만큼 논리 쉬프트 하여 R0에 저장

아래에서 간략한 명령어 종류 및 설명을 확인해 봅시다.

명령어설명
MOV데이터 이동
MVN데이터 이동 및 부언 (negate; 0을 1로, 1을 0으로)
ADD더하기
SUB빼기
MUL곱하기
LSL논리 쉬프트 (왼쪽)
LSR논리 쉬프트 (오른쪽)
ASR산술 쉬프트 (오른쪽)
ROR로테이션 (오른쪽)
CMP비교
ANDBitwise AND
ORRBitwise OR
EORBitwise XOR
LDR불러오기
STR저장하기
LDM여러개 불러오기
STM여러개 저장하기
PUSH스택에 푸시
POP스택에서 팝
B분기
BL링크 하며 분기
BX교환 하며 분기
SWI/SVC시스템 콜
BLX링크 및 교환 하며 분기