ARM 메모리

경로 및 정보 2023. 8. 1. 20:50

https://throwexception.tistory.com/89

 

ARM을 활용한 임베디드 시스템 설계 5 - 메모리

ARM 프로세서 동작 모드 - USER 모드 : 일반 사용자 프로그램 모드 - SYSTEM 모드 : CPSR을 완전히 읽기 쓰기 가능 - Supervisor 모드 : 운영체제를 위한 예외, 커널이나 디바이스 드라이버 처리 - FIQ 모드 :

throwexception.tistory.com

ARM 프로세서 동작 모드

- USER 모드 : 일반 사용자 프로그램 모드

- SYSTEM 모드 : CPSR을 완전히 읽기 쓰기 가능

- Supervisor 모드 : 운영체제를 위한 예외, 커널이나 디바이스 드라이버 처리

- FIQ 모드 : 긴급한 인터럽트 발생시 진입. 빠른 인터럽트 처리를 위한 모드

- IRQ 모드 : 일반 인터럽트 발생시 진입.

- Abort 모드 : 데이터 또는 명령어 거부시 진입

- Undefined 모드 : 패치된 명령어가 정의되지 않을시 진입

* CPSR : Current Program Status Register

 

(1) ARM 프로세서 동작모드

1) 프로그래머 모델

2) 프로세서 동작모드

3) 특권 모드

4) 예외 모드

5) 동작 모드 변경

 

1) 프로그래머 모델

프로그래머 모델이란?

- 컴퓨터 시스템 구조와 동작을 표현한 추상적 개념모델

- 프로그래머가 코드 작성위해 알아야한 최소한 프로세서에 대한 정보

- 프로그램 최적화를 위한 중요 정보

 

프로그래머 모델 구성

- 프로세서 동작모드 : 운영체제를 하드웨어적으로 지원하는 모드

- 레지스터 구성방법 : CPU가 사용가능한 효율적인 저장장소

- 메모리접근방법 : 메모리서 데이터 읽거나 쓸때 적용

- 명령어 셋 :  프로세서가 실행할수있는 명령어 셋

- 예외처리방법 : 시스템의 실사간 처리에 큰 영향을 줌. 실시간 처리 성능 예측과 성능향상에 중요

 

2) 프로세서 동작모드

 

ARM 프로세서 동작 모드

- 프로세서가 프로그램 실행시 권한 설정

- 7개 동작 모드 : 6개 특권 privileged mode + 1개 사용자 모드 user mode

   특권 모드 : 예외 처리, 시스템 자원에 접근 - system 모드, supervisor 모드, fiq 모드, irq 모드, abort 모드 등

   사용자 모드 : 사용자 프로그램 실행 상태, 시스템 자원 접근 제한. 필요시 운영체제에 요청. 동작모드 변경불가

 

3) 사용자 모드

사용자 모드

- 시스템 자원 접근 제한하여 시스템 자원 보호

- 다른 동작 모드 진입 불가

- 변경 시 소프트웨어 인터럽트로 특권모드 진입후 가능

- 사용자 모드서 레지스터 사용 : R0 ~R12 범용레지스터 R13 스택 포인터 R14 링크레지스터 R15 프로그램카운터

 

4) 특권 모드

- 예외 처리하거나 시스템 자원에 접근가능모드

- 시스템 모드 : 운영체제를 위한 모드 운영체제 커널 작업 실행. 시스템 자원 접근 가능. 예외 발생 없이 진입.

- 슈퍼바이저 모드 : 운영체제를 위한 보호모드로 시스템 리셋이 진입시 초기 동작모드. 전원 공급시 가장 먼저 진입

- IRQ 모드 : 일반 인터럽트 모드시 진입

- FIQ 모드 : 고속 인터럽트 발생시 진입. 레지스터 뱅킹 세트 확장. R8~R12까지 5개레지스터 추가하여 8개 레지스터 추가사용

- Abort 모드 : Abort 예외발생시 진입. 명령어나 데이터 메모리 접근 오류시 발생.

   명령어 접근 오류 : 명령어 pre-fetch 과정서 발생 오류

   데이터 접근 오류 : 데이터 alignment 오류, 가상 어드레스 변환오류, 메모리 도메인/ 접근권한 위반 오류 등

- Undefined 모드 : Undefined 예외 발생시 진입. 정의 되지 않은 명령어가 디코딩 시

 

5) 예외 모드

- 특권 모드 중 예외와 관련있는 동작 모드

- 시스템 모드를 제외한 5가지 -> 슈퍼바이저, IRQ, FIQ, Abort, Undefined

- 하드웨어 또는 소프트웨어 예외발생시 진입

- 동작 모드 전확 속도 향상을위해 레지스터 셋 뱅킹

- 시스템 자원에 접근

 

6) ARM 프로세서 동작 모드 설정

- ARM 프로세서 동작 모드 설정은 상태레지스터 CPSR의 동작 모드 필드 M[4:0] 사용

- 사용자 모드 : 10000

- 시스템 모드 : 11111

- 슈퍼바이저 모드 : 10011

- FIQ 모드 : 10001

- 동작 모드 값 변경은 특권 모드에서만 변경가능

 

7) 동작 모드변경

프로세서 동작 모드 변경

- 동작모드 설정 : 상테레지스터의 동작모드 필드 값 변경하여 변경 -> 특권모드서 가능

- 예외 발생하여 특권모드 진입 -> 특권모드에서 동작모드 필드 변경하여 가능

 

 

(2) 레지스터 구성

1) 레지스터 구성

2) 프로그램 상태 레지스터

 

1) 레지스터 구성

ARM 프로세서 구성

- 레지스터 : 모드 37개 32비트 레지스터로 31개 범용, 6개의 상태 레지스터. 프로세서 모드에 따라 사용가능한레지스터가 바뀜

 

특수용도 레지스터

- R13 : 스택 포인터로 각 동작 모드에 사용되는 스택 위치 정보를 저장. R13을 이용하여 스택 관리

- R14 : BL명령어로 실행. 링크 레지스터 PC 내용 복사해서 복귀 위치 저장. 

- R15 : 프로그램 카운터 

- CPSR : 현재 상태 레지스터 . 상태표시 플래그, 동작모드 설정비트, 제어비트 등 포함. SPSR(이전 CPSR 값저장)

 

Thumb state 레지스터 구성

- 레지스터 사용이 크게 재한

- R8 ~ R12 까지 범용 레지스터는 사용 불가

- ARM state처럼 범용 레지스터 뱅킹 이점을 갖지 못함

 

2) 프로그램 상태 레지스터

프로그램 상태 레지스터

- 1개의 CPSR

- 5개의 SPSR : CPSR의 뱅킹 레지스터. 예외 처리 함수에서 사용. 모드 변경시 CPSR 내용을 SPSR로 복사

- condition code flag, control bits

 

컨디션 코드 플래그 필드

- CPU 연산 결과를 반영하는 플래그 필드

- nzcv 4개의 플레그 사용

- N : 연산 결과가 음수

- Z : 연산 결과가 음수

- C : 캐리 발생, 자리빌림, 최상위 비트로 쉬프트되어 나온값

- V : 연산결과가 오버플로시 표시

 

컨트롤 비트 필트

- 인터럽트 허용 여부 결정 -> T/I비트

- 프로세서 동작모드설정 -> 모드설정비트

- 프로세서 상태 표시 -> T 비트

- 예외 발생시 변경

- 특권 모드에서 소프트웨어적으로 변경 가능

* T 비트 :  프로세서의 state 표시. 0이면 ARM state, 1이면 thumb state

* I 비트 : 인터럽트 disable 비트. 1설정시 인터럽트 불허

 

동작 모드 필드

- 동작 모드 필드 값에 따라 프로세서의 동작 모드가 결정됨

블로그 이미지

wtdsoul

,

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=goodkus&logNo=220214389063 

 

도전! 임베디드 OS만들기 - 4.1장(exception vector table 구성하기)

이번 장에서는 exception vector table을 공부합니다. exception vector table은 여러가지 인터럽트와 예외...

blog.naver.com

 

 

이번 장에서는 exception vector table을 공부합니다.

exception vector table은 여러가지 인터럽트와 예외를 처리할수 있는 주소를 모아놓은 테이블입니다.

 

먼저 예외와 동작모드에 관해서 보겠습니다.

 

arm에서의 예외는 총 7가지가 있습니다.

Data Abort : 메모리의 데이터를 읽거나 데이터를 쓰려다가 실패하는 경우 발생.

FIQ, IRQ : 외부 인터럽트가 코어에 전달될 때 발생.

Prefetch Abort : 명령어를 해석하다가 실패하는 경우 발생. 메모리에 존재하는 명령어를 읽어오다가 오류가 생기면 발생.

SWI(Software Interrupt) : 프로그램 내부에서 발생시키는 인터럽트. 소프트웨어 인터럽트라고 함. 시스템 콜의 핵심.

Reset : ARM 코어가 리셋되었을 경우 발생

Undefined Instruction : 읽어온 명령어가 ARM 코어와 코프로세서에 정의디지 않은 명령어일 때 발생.

 

OS나 임베디드 OS를 만들 경우엔 저 예외들은 필수로 들어간다고 생각합니다.

(현재 arm 아키텍쳐를 제일 많이 사용하니 보편적으로 사용하는 최소한의 예외라고 생각합시다.)

    

그리고 ARM에는 7가지 exception과 연결된 일곱가지의 프로세서 동작모드가 있습니다.

 

Abort(ABT)

Fast Interrupt Request(FIQ)

Interrupt Request(IRQ)

Supervisor(SVC)

System(SYS)

Undefined(UND)

USER(USR)

 

ABT, FIQ, IRQ, SVC, UND 모드는 exception이 발생할 때 자동으로 변경되는 동작모드,

SYS USR는 프로세서의 일반적인 동작 모드입니다.

 

예외 동작모드
Prefetch Abort ABT
Data Abort
FIQ FIQ
IRQ IRQ
Reset SVC
SWI
Undefined Undefined

 

예외가 발생할 경우 해당하는 동작모드로 변경을 하려면 cpsr(Current Program Status Register) 0~4번 비트를 수정해 주어야 합니다. 전에 3장에서 보았던 LED를 끄고 켤 때 관련된 레지스터의 비트를 수정하는 것과 같습니다.

 <!--[endif]--> 

모드 약자 특권기능 모드 비트(cpsr[4:0])
Abort abt yes 10111
FIQ fiq yes 10001
IRQ irq yes 10010
Supervisor svc yes 10011
System sys yes 11111
Undefined und yes 11011
USER usr no 10000

cpsr 0~4번 비트에 위의 표에서 나오는 모드 비트값을 넣으면 해당모드로 작동합니다.

근데 자세히보면 모드 비트의 자리수는 0~4번까지 5개의 비트를 사용합니다. 실제로 사용하는 모드는 총 7개뿐인데 말이죠.

방금 보았던 Prefetch Abort예외와 Data Abort예외가 Abort 동작모드에서 처리가 되도록 설정되었지만,

동작모드를 새로 하나 만들고 Prefetch Abort Data Abort를 각각 처리를 할수도 있다는 것을 유추해볼수 있습니다.

(2^5승이니 32.  32개까지 모드를 만들 수 있겠죠.)

 

책에서는 리눅스에서의 동작모드에 대해 엄청 잘 설명해놨습니다.

리눅스에서는 일반적으로 사용자 프로그램은 전부 다 USER모드에서 작동한다. 그러다가 프로그램 안에서 시스템 콜을 호출하면 프로그램은 SWI exception을 발생시키고, ARM 코어가 프로세서의 동작 모드를 SVC 모드로 변경시켜 Software Interrupt를 처리한 후에 다시 USER모드로 복귀한다. 마찬가지로 키보드 입력같은 경우는 대부분의 운영체제에서 IRQ로 처리한다. 우리가 키보드를 누르면 키보드 케이블을 통해 메인보드에 신호가 전달되고, 메인보드에 있는 여러 회로를 거쳐 ARM코어에 IRQ exception이 발생한다. 그러면 ARM 코어는 프로세서 동작 모드를 IRQ 모드로 변경하고 IRQ 관련 처리를 한 다음 USER모드로 복귀한다.”

 

근데 리눅스를 잘 사용하지 않아 조금 거부감이 있는 사람들을 위해 윈도우 측면에서 설명해 보겠습니다.

(왠만하면 위에껄로 이해하세요. 제가 설명하는게 정확한지 모릅니다.)

윈도우에서는 일반적으로 사용자 프로그램은 각각 생성된 윈도우 계정에서 작동한다.[USER모드에서 작동] 그렇게 프로그램에서 작업을 하던 중, 작업하던 프로그램이 멈춰버렸다. 응답이 없는 프로그램을 처리하기 위해서 우측 상단의 X표를 누른다.[SWI exception을 발생시킨다.] 그리고 예외를 처리하기 위해서 프로세서의 모드를 프로그램을 종료시킬수 있는 모드로 변경한다.[SVC 모드로 변경] 마지막으로 응답없는 프로그램을 종료 시키고[Software Interrupt 처리] 다시 프로그램이 켜지기 전의 상태로 돌아간다.[USER모드 복귀]

 

   

이제 예외를 처리하는 방법을 봅시다.

 

exception ARM에서 처리하는 순서.

1. exception이 발생함.

2. exception 모드의 spsr cpsr을 저장함.

3. exception 모드의 lr pc값을 저장함.

4. cpsr의 모드 비트를 변경하여, 해당 exception에 대응하는 프로세서 동작 모드로 진입함.

5. pc exception 핸들러의 주소를 저장하여 해당 exception에서 해야 할 일을 처리함.

 

spsr(Saved Program Status Register) : 각 동작모드에 존재하며, exception이 발생하여 cpsr을 변경해야 할 때 이전 모드의 cpsr spsr에 백업한다.

링크레지스터(lr) : exception의 처리후 복귀하기 위한 pc의 주소를 저장.

 

4번째에 보면 모드비트를 변경하고 나서, 대응하는 프로세서 동작 모드로 진입한다고 되어있습니다.

각 동작모드가 있는 주소를 모아 놓은 것이 exception vector table입니다.

감이 안잡힌다면 포인터 개념으로 생각하면 편합니다.

 

블로그 이미지

wtdsoul

,

https://rninche01.tistory.com/entry/Linux-Memory-Protection

 

checksec스크립트나 peda-gdb, pwndbg를 이용하면 mitigation확인할 수 있음

 

1. ASLR(Address Space Layout Randomization)

메모리 상의 공격을 어렵게 하기 위해 스택이나 힙, 라이브러리 등의 주소를 랜덤으로 프로세스 주소 공간에 배치함으로써 실행할 때마다 데이터의 주소가 바뀌게 하는 기법

 

현재 실행되고 있는 프로세스 주소 매핑 확인

cat /proc/self/maps 

→ 주소가 랜덤하게 바뀌는 것 확인

 

ASLR 활성화 및 비활성화

echo [0, 1, 2] > /proc/sys/kernel/randomize_va_space

[0] : ASLR 비활성화

[1] : 랜덤 Stack, Library 활성화

[2] : 랜덤 Stack, Library, Heap 활성화

 


2. NX(Never eXecute bit), DEP(Data Execution Prevention)

데이터 영역에서 코드가 실행되는 것을 방지하는 기법으로 스택이나 힙에서 실행 권한을 없애 쉘 코드 실행을 막아주는 기법이다. 윈도우에서는 DEP라고 부르고 리눅스에서는 NX bit라고 불러서 결국 둘 다 동일한 말

 

NX 비활성화 옵션

gcc -z execstack 

 


3. Stack Canary

함수 진입 시 스택에 SFP와 return address값을 저장할 때, 이 값을 공격자로부터 덮어씌워지는 것을 보호하기 위해 스택 상의 변수들의 공간과 SFP사이에 특정한 값을 추가하는데 이 값을 Canary라고 부른다. 함수가 종료할 시점에 Canary값의 변조 유무로 bof를 탐지할 수 있으며 변조 시 프로그램이 종료된다. 보통 SFP앞에 카나리 있음.

 

Canary 종류

  • Terminator Canary : NULL(0x00), CR(0x0d), LF(0x0a), EOF(0xff)와 같은 Terminator 문자를 포함시키는 Canary로 종료 문자들 때문에 Canary값을 알고 있어도 똑같이 작성하여 공격이 불가능
  • Random Canary : Canary값이 랜덤으로 계속해서 생성하는 방법
  • Radom XOR Canary : Canary값을 모든 제어 데이터 또는 일부를 사용해 XOR-scramble하여 생성

canary 활성화 및 비활성화 옵션

[비활성화]
gcc -fno-stack-protector

[활성화]
gcc -fstack-protector-all

 


4. RELRO(Relocation Read-Only)

GOT Overwrite와 같은 공격에 대비해 ELF 바이너리 또는 프로세스의 데이터 섹션을 보호하는 기법이다. 바이너리 컴파일 시 Full-RELRO 옵션을 주면 .ctors, .dtors, .jcr, .dynamic, .got섹션이 Read-Only가 됨 → GOT Overwrite 불가능

 

Lazy Binding(Lazy Linking, on-demand symbol resolution)

모든 함수의 주소를 한 번에 로딩하지 않고 함수를 처음 호출한 시점에 공유 라이브러리에서 해당 함수의 주소를 알아오는 것을 Lazy Binding이라고 한다.

 

Now Binding

프로그램이 실행될 때 해당 프로그램에서 사용하는 모든 함수들의 주소를 읽어와 got영역에 저장

 

추가 설명)

Dynamic Linking 방식으로 컴파일된 바이너리에서 라이브러리 함수를 호출할 때 실제 함수의 주소를 모르기 때문에 동적으로 알아오기 위해 plt, got를 이용한다. 처음 함수를 호출할 때는 plt영역의 got를 참조하고 뭐 여러가지 과정을 거치고 dl_runtime_resolve()로 가서 dl_fixup()에서 드디어 실제 함수의 주소를 got에 넣어준다. 이후에 다시 함수를 호출할 때는 위와 같은 복잡한 과정을 거치지 않고 걍 got에 있는 함수를 호출한다.

 

[No-RELRO]

 

[Partial-RELRO]

 

[Full-RELRO]

 

추가 설명)

Full RELRO를 사용하면 GOT가 Read-Only인 상태여서 Partial RELRO보다 보안상 더 안전할 것 같은데 Full RELRO보다 Partial RELRO가 더 널리 사용되는 이유는 Full RELRO의 경우 프로세스가 시작될 때 링커에 의해 모든 메모리에 대해 재배치 작업이 일어나 실행이 느려지기 때문이다.

 


5. PIE(Position Independent Executable)

PIE는 위치 독립 코드로 이루어진 실행 가능한 바이너리이며 바이너리 주소가 상대적인 주소로 랜덤하게 매핑시키는 기법이다. 모든 심볼을 상대주소로 작성하므로 Base Address가 달라지게 함으로써 모든 심볼 주소가 실행할 때마다 달라져 ASLR과 같은 효과를 가질 수 있다.(Base Address + Relative offset, 참고로 Base Address는 매번 변경되지만 offset은 동일)

 

참고로 PIE는 OS의 ASLR기능에 의존하므로 OS의 ASLR이 dsiable되어 잇으면 randomize 불가하며 더 구체적으로는 OS loader의 영향을 받는다.

 

PIE 활성화 및 비활성화 옵션

[비활성화]
gcc -no-pie

[활성화]
gcc -fPIE -pie

 


6. ASCII-Armor

RTL공격에 대응하기 위한 방법으로 공유 라이브러리 영역의 상위 주소에 0x00을 포함시킨다. ASCII-Armor가 적용되면 라이브러리를 공유 라이브러리 영역에 할당하지 않고 텍스트 영역의 16MB아래의 주소에 할당된다. 따라서 라이브러리 영역의 주소 최상위 1byte가 NULL로 인식된다. bof공격을 위해 string관련 함수(strcpy, strcat, ...)를 사용하게 되면 문자열을 읽다가 NULL값이 나오면 중지되므로 라이브러리 함수를 호출하는 그 때 최상위 1byte가 NULL값이므로 동작이 끝나 버린다.

→ GOT Overwrite로 우회 쌉가능

 

 


Reference)

 

02.Protection Tech - TechNote - Lazenca.0x0

페이지 TechNote 02.TechNote 배너의 맨 끝으로 배너의 맨 처음으로 02.Protection Tech 메타 데이터의 끝으로 건너뛰기 Lazenca.0x0님이 작성, 10월 13, 2019에 최종 변경 메타 데이터의 시작으로 이동

 

 

Linux 환경에서의 메모리 보호기법을 알아보자

Researcher salen salen의 다른 글 더 보기 CONTENTS Linux 환경에서의 메모리 보호기법을 알아보자 1 ASLR, NX, ASCII-Armor, Stack canary 에 대해 알아보자 :) Linux 환경에서의 메모리 보호기법을 알아보자 2 RELRO에

bpsecblog.wordpress.com

 

'경로 및 정보' 카테고리의 다른 글

ARM 메모리  (0) 2023.08.01
exception vector table 구성  (0) 2023.08.01
임베디드 레시피 Debug  (0) 2023.07.18
Content-Type 이란  (0) 2023.06.27
CAN TP Protocol  (0) 2023.06.22
블로그 이미지

wtdsoul

,

https://velog.io/@embeddedjune/%EC%9E%84%EB%B2%A0%EB%94%94%EB%93%9C-%EB%A0%88%EC%8B%9C%ED%94%BC-%EC%9A%94%EC%95%BD-%EB%B0%8F-%EC%A0%95%EB%A6%AC-Chapter-8.-Debug

 

[임베디드 레시피 요약 및 정리] Chapter 8. Debug

지난 chapter 1~7의 모든 내용이 머릿속에 있다는 가정하에 이야기를 전개한다.이 chapter는 가상의 시스템을 가정하고 특정 문제상황에서 어떻게 디버깅을 하면 좋을지 설명한다.따라서 시스템의

velog.io

0. 들어가기 전에

  • 지난 chapter 1~7의 모든 내용이 머릿속에 있다는 가정하에 이야기를 전개한다.
  • 이 chapter는 가상의 시스템을 가정하고 특정 문제상황에서 어떻게 디버깅을 하면 좋을지 설명한다.
  • 따라서 시스템의 특징에 대해 먼저 정의를 내리고 이야기를 진행해야 한다.
  • 이 시스템은
    1. High vector를 사용한다. 따라서 EVT와 bootloader는 0xFFFF_0000번지에 존재한다.
    2. ISR을 SYS mode에서 처리한다. (∵ Nesting interrupt & mode change를 용이하게 하기 위해)
    3. SVC mode에서 user application과 kernel을 모두 실행한다.
    4. R13_SYS의 시작주소는 0x00FE_E730이다.

1. Reset 디버깅

1.1. Interrupt lock

  • 우선 PC값이 High EVT의 reset handler를 가리키고 있기 때문에 HW reset이 발생했음을 눈치챌 수 있다.
  • Reset이 발생했으니 당연히 SVC mode다.
  • 안타깝게도 reset은 일반적인 상황이 아니다보니 제대로 context가 남아있지 않을 때도 많기 때문에 SPSR의 정보를 신뢰할 수 없다. 현재 우리는 지푸라기 잡는 심정으로 LR을 믿어보는 수 밖에 없다.
  • LR이 가리키는 0x00EDCF19로 이동해보자. 만일 LR이 제대로 정보를 가지고 있다면, 이 근방에서 reset이 발생했을 것이다.

  • 해당 주소로 이동해보니 0x00ED_CF18번지에서 b명령어로 자기자신으로 branch 하는 line을 발견할 수 있다.
  • 원인은 다름아니라 while(1); 무한 loop를 발생시키는 코드 때문이었다.
  • 그래서 task가 끝나지 못하고 watch dog task에게 report해서 timer를 reset 시키지 못해 HW reset이 발생한 것이다.
  • 사실 굉장히 운이 좋은 경우다. Reset은 LR의 유효성을 보장하지 않는다. 그 위의 Interrupt_Lock() 덕분에 blx명령어가 실행돼 LR에 복귀주소가 기록되는 바람에 LR에 유용한 정보가 남아있었던 것이다.
  • 게다가 MCU reset pin에 신호를 걸어주도록 설계된 시스템의 경우에는 HW reset이 났을 때 모든 레지스터와 메모리 정보가 싹 사라지기 때문에 디버깅이 매우 힘들어진다.

1.2. Task lock

  • 이번에는 조금 더 어려운 상황을 생각해보자.

  • 이번에도 PC는 EVT의 reset handler를 가리키고 있으므로 HW reset이 발생했음을 눈치챌 수 있다.
  • 이번에도 LR을 믿어보자. 0x0000_1323번지를 덤프해보면 다음과 같다.

  • LR은 bl명령어를 만나 Interrupt_Free()를 수행한 뒤 복귀주소로 0x0000_1323번지를 담고 있었다.
  • 그리고 pop명령어는 R7, PC값을 반환했다. 저 PC값이 무엇이었는지 확인하기 위해 R13이 가리키는 주소 0x0116_FD3C로 이동해보자.

  • R13은 0을 가리키고 있다. Stack은 full descending stack이니까 pop을 2회 하면 주소가 높아졌을 것이다.
  • 즉, SP를 두 칸 아래로 옮기면, 0x00FC_4584는 R7, 0x00ED_CEB1은 PC에 저장됐을 것이다.
  • 이제 원래 PC값을 알게 됐으니 0x00ED_CEB1번지를 덤핑해보자.
  • 덤핑해보니 방금 다뤘던 시나리오처럼 무한 loop를 발생하는 while(1)문이 들어있었음을 확인할 수 있다. 따라서 task가 끝나지 못하고 watch dog에 의한 HW reset이 발생했음을 알 수 있다.
  • 이번에는 LR과 SP를 활용해서 stack backtracing을 통해 문제 원인을 밝힐 수 있었다.

1.3. 0x0 branch

  • 이번에도 reset이 발생했는데 특이하게 PC가 EVT를 가리키지 않고 low vector 일 때의 EVT를 가리키고 있다.
  • 즉, HW reset이 아니라 SW적인 원인으로 발생한 reset임을 눈치챌 수 있다.
  • 우선 이번에도 우리가 할 수 있는건 LR을 따라가는 것 뿐이니 0x00ED_CF3B로 이동해보자.

  • 당장 눈에 보이는건 blx명령어인데, 이전까지 Thumb mode로 실행되고 있다가 이때부터 ARM mode로 실행됐다.
  • blx명령어를 만나고 복귀주소로 LR에 집어넣은 것 같으니 운이 좋다.
  • 위를 보니 memset()함수가 보인다. 범인을 찾은 느낌이 든다.
  • memset()함수는 함수의 시작주소로부터 60-bytes를 0으로 초기화한다.
  • 따라서 R0에는 0x0이 들어가고, 그 주소로 branch를 해버렸으니 reset이 발생한 것이다.

2. Abort exception 디버깅

  • Abort는 reset보다 상대적으로 문제 원인을 찾기 매우 쉽다.

2.1. Data abort

  • 우선 context를 보면 ABT mode이며 PC가 0x10로 abort handler를 가리키고 있으므로 data abort가 발생했음을 알 수있다. 따라서 LR - 0x8 주소로 이동하면 된다.
  • LR이 0x00ED_CF26이므로 해당 주소로 이동한 뒤 0x8을 뺀 line을 보면, STR R0,[R1] 명령어가 있다.
  • R1이 0xFFFF_FFFF값을 가지고 있는데, 현재 시스템의 memory map에는 해당 주소가 정의돼있지 않다.
  • 즉, 접근할 수 없는 주소에 write하려는 시도 때문에 data abortion이 발생했음을 알 수 있다.
  • C 소스코드를 보니 HWIO_ADDR이라는 전역변수가 0xFFFF_FFFF를 가리키는데 여기에 0x8을 쓰려다가 abortion이 발생했음을 알 수 있다. 잘못된 주소를 가리키고 있으니 초기화 값을 수정해주면 오류를 해결할 수 있다.

2.2. Prefetch abort

  • Context를 보면 ABT mode이며 PC가 0x0C로 prefetch handler를 가리키고 있으므로 prefetch abort가 발생했음을 알 수 있다. 따라서 LR - 0x4 주소로 이동하면 된다.
  • 그런데 LR을 보니 0x0000_0002라는 이상한 값을 갖고 있다. 무슨 이유에선지 LR이 corrupt 됐음을 직감할 수 있다.
  • 단서로 사용할 수 있는 다음 후보 R13을 바라보자. 물론 ABT mode의 R13을 봐서는 안 된다.
  • SPSR을 보니 10011로 이전 mode가 SVC임을 알 수 있다.
    • R14_SVC는 0xFFFF_FFFF를 가리키고 있다. 이쪽도 LR이 corrupt 됐다.
    • R13_SVC는 0x0116_FD24를 가리키고 있으니 그쪽으로 가보자.

  • 일단 SP를 포함해 인근 영역이 전부 0xFF로 가득찬 것을 보아하니 심상치 않은 일이 발생했음을 알 수 있다.
  • Stack을 backtracing 해보면, 가장 최근에 push한 정보는 0x00ED_D0FD이므로 우선 이 주소로 이동해보자.
  • 이동해보니 bl 0xEDCE52 명령어가 보인다. bl 명령어를 만나 복귀할 주소를 LR에 남겨놓은 흔적을 볼 수 있다. ([※] 근데 왜 LR이 stack에 저장돼있었는지는 모르겠네요.)
  • 이번에는 0xEDCE52로 이동해보자. void chaos(void)라는 함수가 시작되는 부분이다.
  • 아하, 이 함수에서 memset((void *)(LocalBuffer - 10), 0xFF, 40)을 호출하는 것을 알 수 있다.
    • 지역변수로 word형 LocalBuffer[10]을 선언하고
    • LocalBuffer 시작주소로부터 20-bytes 밑(void * type이 2-byte 크기라고 가정하자)에서부터 40-bytes만큼을 0xFF로 초기화한다.

  • 정리하면 위 그림의 설명과 같다.

2.3. Interrupt 처리 중 abort

  • 이번에도 PC가 abort handler를 가리키는 것으로 보아 data abort가 발생했음을 알 수 있다.
  • SPSR값을 보니 11111로 이전 mode가 SYS였다. 즉, 이번 abortion은 ISR을 처리하다가 발생했음을 눈치챌 수 있다.
  • R14값 0x476A로 이동해보니 역시 clock_tick_ISR()이라는 ISR을 처리하다가 abortion이 발생했다.

  • Data abort이므로 LR - 0x8인 0x0000_4762부터 바라보니 심상치 않은 어셈블리를 볼 수 있다.
  • ldmia명령어가 계속 반복되며 딱 봐도 C 코드와 일치하지 않는 mnemonic이다.
  • 높은 확률로 이쪽 주소 영역의 코드가 corruption이 발생한 것이므로 0x0000_4762번지에 write breakpoint를 걸어 누군가 이쪽 주소에 데이터를 쓰려고 시도하면 break가 걸리도록 해보자.
  • 확인해보니 memset((void *)(functionsMarcel.HWIO_init), 0xABCD, 10);이라는 함수가 원인임을 발견했다.
    • functionsMarcel.HWIO_init이 어떻게 초기화 됐는지 확인해보니
    • functionsMarcel = {clock_io, clock_tick_isr};로 clock_tick_isr이 HWIO_init함수 포인터가 가리키는 함수로 초기화 돼있음을 확인할 수 있다.
  • 따라서, memset()에 의해 clock_tick_isr() ISR의 시작주소로부터 10-bytes의 영역이 0xABCD로 초기화 됐고,
  • Little endian이므로 해당 영역의 opcode가 CDAB로 채워져있었음을 알 수 있다.

3. Memory 불량

  • 지금까지 우리는 정말 다양한 exception 시나리오와 대처방법을 배웠다.
  • 이번에는 정말 웃기지도 않는 황당한 경우를 보면서 ‘정말 다양한 원인으로 문제가 발생하는구나’라고 느껴보자.

  • 계속되는 reset 및 abort로 인해 메모리를 덤프해보니 위와 같았다.
  • 뭔가 규칙성이 느껴지지 않는가? 특정 열만 0xFD인 규칙성이 보인다.
    • 0xFF이면 1111_1111인데, 0xFD는 1111_1101이다.
  • 즉, 메모리의 특정 bit가 고장이 난 SDRAM 불량인 경우다.
  • 이런 경우는 진짜 오랜 개발경험이 없는 한 전혀 감이 안 오는 상황이다. Stack이 문제인가? 실행 binary를 잘

'경로 및 정보' 카테고리의 다른 글

exception vector table 구성  (0) 2023.08.01
Linux Memory Protection  (0) 2023.07.19
Content-Type 이란  (0) 2023.06.27
CAN TP Protocol  (0) 2023.06.22
source insight 메뉴얼 등  (0) 2023.06.20
블로그 이미지

wtdsoul

,

https://way-be-developer.tistory.com/243

 

HTTP Request, Content-Type 이란?

Content-Type 이란? https://developer.mozilla.org/ko/docs/Web/HTTP/Headers/Content-Type Content-Type - HTTP | MDN Content-Type 개체 헤더는 리소스의 media type을 나타내기 위해 사용됩니다. developer.mozilla.org MDN 에서는 Content-Typ

way-be-developer.tistory.com

Content-Type 이란?

https://developer.mozilla.org/ko/docs/Web/HTTP/Headers/Content-Type

 

Content-Type - HTTP | MDN

Content-Type 개체 헤더는 리소스의 media type을 나타내기 위해 사용됩니다.

developer.mozilla.org

MDN 에서는 Content-Type을 다음과 같이 정의하고 있습니다.

 

 
응답 내에 있는 Content-Type 헤더는 클라이언트에게
 
반환된 컨텐츠의 컨텐츠 유형이 실제로 무엇인지를 알려줍니다.

 

말이 조금 어려우니 정리를 해봅니다.

 
응답 헤더에 Content-Type을 포함할 수 있으며
 
이는 응답의 유형 (보통 Body의 데이터 유형)이 어떻게 되는지
 
클라이언트 (응답을 받을 시스템 or 사용자)에게 알려주는 것

여기서 클라이언트는 응답을 받는 주체로

페이스북에 API 요청을 날려서 유저의 게시글 데이터를 받아오는 상황으로 예를 들자면,

요청을 날릴 때에는 페이스북이 클라이언트가 되고

페이스북에서 게시글 정보를 보내줄 때에는 이 정보를 받는 주체가 클라이언트가 됩니다.

 

 

한편 웹에 대해 공부한다면 이에 대해서는 꼭 알아야할 정보입니다.

따라서 오늘은 해당 방식에 대해서 공부를 해보려고 합니다.

 

 Content-Type

일반적으로 Content-Type 의 유형으로 많이 사용하는 타입으로는 3가지가 있습니다.

"application/json", "application/x-www-form-urlencoded", "multipart/form-data"

 

 

application/json

이는 응답 데이터가 Json 일 때 사용합니다.

{ Name : 'John Smith', Age: 23}

이 데이터를 받는 서버 입장에서 위 글자는 단순 문자열일 뿐입니다.

따라서 이 문자열이 단순한 문자열이 아니라 json 데이터다!

라는 것을 알리기 위해서는 application/json 을 사용해야 합니다.

 

application/x-www-form-urlencoded
 
form 내부의 input 요소의 데이터를 &으로 분리하고, "=" 기호로
 
값과 키를 연결하는 key-value tuple로 인코딩되는 값입니다.
 
영어 알파벳이 아닌 문자들은 percent encoded 으로 인코딩됩니다

 

쉽게 풀어 설명하면 form 의 데이터를 전송할 때 input 의 값을 다음과 같은 형식으로 만들어 요청을 서버에 날립니다.

 
POST / HTTP / 1.1
 
host : localhost
 
 
 
// 여기
 
Name=Smith&Age=23

 "Name=Smith&Age=23" 형식으로 "key=value&key=value" 형태라는 것을 서버에 알립니다.

한편 브라우저에 의해서 인코딩 되어 보내집니다.

(대부분의 브라우저는 W3 규격을 충실하게 따르고 있기 때문에, 폼을 submit 할 때 body를 적절하게 encoding 해줍니다.)

 

HTML 폼을 만들어 데이터를 전송할 때 많이 사용 되었던 헤더 입니다.

그러나 요즘은 이 헤더의 사용이 예전에 비해 많이 줄었습니다

 

 

 

 

multipart/form-data

이는 보낸 응답 데이터에 파일이 첨부 될 수 있음을 알리는 것 입니다.

HTML 폼에선 enctype="multipart/form-data" 속성을 명시함으로써 사용할 수 있습니다.

위 속성을 명시하면 브라우저는 서버에 요청을 날릴 때 헤더의 Content-Type을 mulipart/form-data 으로 설정 합니다.

'경로 및 정보' 카테고리의 다른 글

Linux Memory Protection  (0) 2023.07.19
임베디드 레시피 Debug  (0) 2023.07.18
CAN TP Protocol  (0) 2023.06.22
source insight 메뉴얼 등  (0) 2023.06.20
ELF 파일 포맷  (0) 2023.05.30
블로그 이미지

wtdsoul

,

CAN TP Protocol

경로 및 정보 2023. 6. 22. 13:12

https://piembsystech.com/can-tp-protocol

 

CAN-TP Protocol

Table of contentsIntroduction to CAN-TP (ISO 15765) TutorialTypes of Addressing in CAN-TP (ISO 15765) ProtocolBasic Addressing in CAN-TP (ISO

piembsystech.com

 

CAN-TP is a transport protocol that enables large data packets to be transmitted over the CAN bus, which has a limited message size. The protocol allows for the segmentation and reassembly of data packets, so that they can be transmitted in multiple smaller frames over the CAN bus.

Types of Addressing in CAN-TP (ISO 15765) Protocol

The main purpose of addressing in CAN-TP is for how to identify, either it is a non CAN-TP message or CAN-TP message. To identify it, there ae 2 types of addressing defined in the ISO 15765-2 standard.

  1. Basic Addressing.
  2. Extended Addressing.

Basic Addressing in CAN-TP (ISO 15765)

The CAN-TP Protocol basic addressing mode is called a normal addressing mode were to identify the CAN message or CAN-TP, we are using the CAN Identifier. So for this, there will be some identifier will be for CAN-TP, where if any message will receive then the server will understand that this TP message. The advantages of this type of addressing are that full 8 bytes of the data packet can be sent as data.

In basic addressing, each message is addressed to a specific node on the network using a unique identifier called a “Node Address.” Each node on the network is assigned a unique Node Address, which is typically configured by the user or by a configuration tool.

When a node wants to send a message, it includes the destination Node Address in the message header. When the message is received by the other nodes on the network, they compare the destination Node Address in the message header to their own Node Address. If the Node Address matches, the node accepts the message and processes it. If the Node Address does not match, the node ignores the message.

Extended Addressing in CAN-TP (ISO 15765)

Extended Addressing is a feature of the ISO 15765-2 Transport Protocol (CAN-TP) that allows messages to be addressed to a larger number of nodes on a Controller Area Network (CAN) than is possible with Basic Addressing. In Extended Addressing, the identifier field of the CAN message header is used to transmit the destination Node Address, which can be up to 29 bits in length. This allows for a much larger address space, with up to 536,870,912 possible Node Addresses, compared to the 8-bit Node Addresses used in Basic Addressing.

Extended Addressing also allows for dynamic addressing, where nodes can be added or removed from the network without requiring reconfiguration of the Node Addresses. This is accomplished through the use of a “Dynamic Addressing” scheme, where a unique “Arbitration ID” is assigned to each node, and the Node Address is transmitted in the data portion of the message.

When a node sends a message using Extended Addressing, it includes the destination Node Address in the identifier field of the CAN message header. When the message is received by the other nodes on the network, they compare the destination Node Address in the message header to their own Node Address. If the Node Address matches, the node accepts the message and processes it. If the Node Address does not match, the node ignores the message.

This addressing mode is the CAN-TP addressing mode where the 1st byte of the CAN data field will be used for the additional elements of the address whereby it reducing the data payload by one byte. The primary task of the transport protocol is to transfer messages which cannot be transmitted as a single Protocol Data Unit (PDU) due to their length. Messages which contain more data that can be transmitted within a single PDU are segmented by means of the transport protocol and divided into multiple, separate PDUs.

This procedure can also be implemented on the data link layer. The segmentation of the message must then be carried out in PDUs of the corresponding data link protocol. So to send the data like CAN, the CAN-TP Protocol has been designed. Let us discuss how it can send multiple frames.

Different Types of Frames in CAN-TP (ISO 15765)

So to make it possible, the CAN-TP also having 2 types of frames. Single Frame and Multi-Frame. Again the Multi-frame is having 3 types of frames. So total 4-types frames are there in CAN-TP:

  1. Single Frame (0x0).
  2. Multi-Frame.
    • First Frame (0x1).
    • Consecutive Frame (0x2).
    • Flow Control Frame (0x3).

Single Frame (SF – 0x0) in CAN-TP (ISO 15765)

 
NetApp Jumbo Frames All You need T...
 
 
Pause
Unmute
 
Loaded: 40.01%
Remaining Time 6:13
Fullscreen
NetApp Jumbo Frames All You need To Know

In the ISO 15765-2 Transport Protocol (CAN-TP), a Single Frame is a type of message that is used to transmit data that can fit within a single CAN data frame. A Single Frame consists of a header and a data field, where the header contains information about the message, and the data field contains the actual data to be transmitted.

Single Frames are used to transmit small amounts of data, typically up to 7 bytes, that can be sent in a single CAN data frame. Single Frames are the simplest and most efficient way to transmit data in CAN-TP, as they require only one message to be sent and do not require any additional message framing or flow control mechanisms.

CAN-TP: Single Frame Format

If the data payload is 7-bytes or less than that, then the single frame will be used for the data transfer in CAN-TP Protocol. Where the first byte of the data field is used for addressing. Again this byte is divided into two divisions, where the MSB 4-bit is used for addressing of TP frame type called PCI (Protocol Control Information). The LSB 4-bit is used for the DLC (Data Length Code).

Example of Single Frame in CAN-TP (ISO 15765)

The below table shows an example of how the CAN-TP implemented in CAN Data field using Single Frame.

05 01 02 03 04 05 AA AA

CAN Datafield with CAN-TP Using Single Frame (Request: Client –> Server)

  1. PCI Header (0x05): 1 Byte Protocol Control Information Header. Where,
    • Last 4 MSB is “0” and it defines the Frame Type is Single Frame.
    • First 4 bit LSB is “5” and it defines the payload or datalength transmitted in Single Frame.
  2. Payload: Next 5 bytes are the original data (01 – 05) sent by the Transmitter ECU and 2 byte is empty (NULL).

Generally, if you are going to send less than 8 byte, you can use the Single Frame else to send the more than that you have to use the other 3 frames. 0xAA or ox55 is filled for NULL data bytes to make sure that no junk or corrupted data sent by the transmitter.

First Frame (FF – 0x1) in CAN-TP (ISO 15765)

In Controller Area Network – Transport Protocol (CAN-TP) ISO15765-2, the “First Frame” refers to the first message frame in a multi-frame message. This message type is used to initiate the transmission of a large data block. It includes information such as the total size of the data block and the size of each subsequent frame.

When a large message is sent over the CAN bus, it is divided into several smaller frames to ensure reliable and efficient transmission. The First Frame is used to initiate this process and contains important information about the message, such as its total length and the number of frames that will be used to transmit it.

The structure of the First Frame message is defined by the ISO15765-2 standard and includes a header that contains information about the message, such as the message length and a unique identifier (ID) that allows the recipient to associate the message with the correct application. The data field of the First Frame contains the first part of the message data, up to a maximum of 4096 bytes.

Once the First Frame has been successfully transmitted and received, the following frames, known as Consecutive Frames, are sent to transmit the remaining parts of the message data. The use of multiple frames allows for more efficient use of the available bandwidth on the CAN bus and ensures reliable transmission even in noisy environments.

The first frame is an initial message of the multi-frame message packet in CAN-TP Protocol. It is used when more than 6 or 7 bytes of data segmented must be communicated. The first frame contains the length of the full packet, along with the initial data. 

CAN-TP: First Frame Format

In FF, the first 2-bytes are used for the PCI, where the MSB 4-bit of the 1st byte is used for the type of frame and the LSB 4-bit and next 1-byte (total 8+4 = 12 bit)of the CAN data field is used for DLC (2^12 = 4096 data bytes). so in FF, only 6-bytes of the data can be transferred for the first time. This frame is responsible for sending the information to the receiver about the information as to how many total data bytes he is going to send.

Example of First Frame in CAN-TP (ISO 15765)

The below table shows an example of how the CAN-TP implemented in CAN Data field using First Frame.

10 64 01 02 03 04 05 06

CAN Datafield with CAN-TP Using First Frame (Request: Client –> Server)

  • PCI Header (0x1064): In FF, it is 2 byte.
    • Last 4 MSB is “1” is defining the Frame Type is First Frame.
    • First 4 LSB plus next 1 byte (Total = 4 LSB + 1 Byte) is Payload. 0x0 + 0x64 = 0x64 in decimal, it is 100 byte of data.
  • Payload: Next 6 bytes (01 – 06) are the original data that is sending to the receiver from total 100 bytes. So remaining 94 bytes will be sent consicutively by using the Consecutive Frames.

Consecutive Frame (CF) in CAN-TP (ISO 15765)

The Consecutive Frame or message type is used to transmit the data in consecutive segments. Each CF message contains a segment of data, as well as a sequence number that identifies the position of the segment within the larger data block.

The Consecutive Frame (CF) messages are used to transmit the actual data in a large data block. The size of each CF message is typically determined by the underlying CAN bus and network topology. The sequence number included in each CF message is used to ensure that the segments of data are received and reassembled in the correct order.

The primary task of the transport protocol or you can also CAN-TP Protocol is to transfer messages which cannot be transmitted as a single Protocol Data Unit (PDU) due to their length. Messages which contain more data that can be transmitted within a single PDU are segmented by means of the transport protocol and divided into multiple, separate PDUs. This procedure can also be implemented on the data link layer. The segmentation of the message must then be carried out in PDUs of the corresponding data link protocol.

CAN-TP: Consecutive Frame Format

In CF, the 1st byte is used for PCI byte, whereas the MSB 4-bit is defined for the type of frame and the LSB 4-bit is defined for the Sequence number. The sequence number always starts at 1 and increments with each frame sent (like as 1, 2,…, 15, 0, 1,…), with which lost or discarded frames can be detected. Each consecutive frame starts at 0, initially for the first set of the data in the first frame will be considered as 0th data. So the first set of CF(Consecutive frames) starts from “1”. There afterwards when it reaches “15”, it will be started from “0” by resetting the buffer register.

Example of Consecutive Frame in CAN-TP (ISO 15765)

The below table shows an example of how the CAN-TP implemented in CAN Data field using Consecutive Frame.

20 01 02 03 04 05 06 07

CAN Datafield with CAN-TP Using Consecutive Frame (Request: Client –> Server)

  • PCI Header (0x20): Here 20 is the 1 Byte PCI in CF.
    • Last 4 MSB is the Frame Type is “2” that defines the Consecutive Frame.
    • First 4 LSB is the Index of number or counter of blocks data is sends by the Transmitter ECU or node.Here it is “0” for the first consecutive frame.
  • Payload: Next remaining 7 bytes (01 – 07) are the original data to be sent to the receiver ECU or node.

Flow Control (FC) Frame in CAN-TP (ISO 15765)

The FC frame is the third and final message type in a three-frame sequence used for transmitting large data blocks that exceed the maximum size that can be transmitted in a single CAN message. This message type is used to manage the flow of data between the transmitter and the receiver. It includes information such as the number of consecutive frames that can be transmitted before the receiver must send an acknowledgement message.

The FC frame is sent by the receiver after it receives a CF message from the transmitter. The FC frame includes information such as the sequence number of the last received CF message, the status of the flow control mechanism, and the number of consecutive frames that can be transmitted by the transmitter before the receiver must send an acknowledgement message.

The information in the FC frame is used to manage the flow of data between the transmitter and the receiver to ensure that data is transmitted efficiently and reliably. By using flow control, the receiver can indicate to the transmitter how much data it can receive at any given time, preventing the transmitter from overloading the receiver with data and causing buffer overflow or other issues.

In conclusion, the flow control mechanism of the CAN-TP Protocol is used to configure the sender to match the properties of the receiver (timing, available receive buffer, readiness to receive). The FCF is always sent by the receiver so that to inform about the receiver capability of how the transmitter is going to send the data. 

CAN-TP: Flow Control Frame Format

 

The flow control frame contains 3 bytes which together form a PCI in CAN-TP Protocol.

  • The first byte begins in the upper four bits with a value of 3, indicates that there is flow control. In the four least significant bits of the first byte shows a Flow Status (FS). Thus the transmitter to the receiver can signal whether a segmented transmission Clear To Send (CTS), Wait or Overflow.
    • FS = 0: Clear to Send.
    • FS = 1: Wait.
    • FS = 2: Overload.
  • The second byte BS stands for Block Size and shows how many consecutive frames need to be sent. So the Block Size is nothing but the number of Consecutive Frame.
  • The last byte STmin shows the minimum separation time between consecutive frames to be noticed.

Example of Flow Control (FC) Frame in CAN-TP (ISO 15765)

30 0A 14 AA AA AA AA AA

CAN Datafield with CAN-TP Using Consecutive Frame (Request: Server –> Client)

  • PCI Header: The total Flow Control Frame is PCI data and it is 3 byte.
    • 0x30: First 4 MSB is “3” and it defines the Type of Frame that is Flow Control and Last 4 LSB is “0” that defines the Flow Status that is clear to send.
    • 0x0A: This second byte is a 1 byte Block Size (BS) nothing but the number of Consecutive Frames. So here the receiver is informing to transmitter ECU to send the next 0x0A or 10 (in decimal) blocks or consecutive frames.
    • 0x14: This third byte is a 1 byte STmin Time period in milli Second. Here it is 0x14 nothing but the 20 (in decimal). That means the Transmitter ECU will send each CF in the delay of 20 milli second to the receiver ECU or node.

An ISO-TP frame is always 8 bytes long and unneeded bytes filled with the padding byte as 0xAA or 0x55.

Working Principle of CAN-TP (ISO 15765)

There are two types of data transfer methods of CAN-TP Protocol: single-frame data transmission and multiple data transmission. The single-frame data transmission (Unacknowledged Unsegmented Data Transfer) and the Multi-frame Data transmission (Unacknowledged Segmented Data Transfer). In a CAN frame, there is a maximum of 8 data bytes of user data. The data length of the ISO TP message can reach a maximum of 4095 bytes. If an ISO TP message length exceeds the data length of 8 data bytes, the UDS message must be segmented.

The CAN-TP protocol works by dividing large data into smaller packets called Transport Protocol Data Units (TPDUs). These TPDUs are then transmitted over the CAN bus, with each TPDU containing a sequence number and checksum to ensure data integrity. The receiver then reassembles the TPDUs into the original data message.

Single Frame Communication

CAN-TP ISO15765 Single Frame Communication

Multi-Frame Communication

CAN-TP for Multi-Frame Communication

Below is an example of how the CAN-TP protocol could work in a client-server communication scenario:

  1. The client sends a large data message to the server, which exceeds the maximum size of a single CAN message.
  2. The client software breaks the large message into smaller TPDUs, each with its own sequence number and checksum.
  3. The client software sends the TPDUs to the server over the CAN bus by using the First Frame (FF).
  4. The server software receives the TPDUs and verifies the sequence numbers and checksums to ensure that all packets have been received and that the data is intact.
  5. Once all TPDUs have been received and verified, the server software reassembles the TPDUs into the original data message.
  6. The server software then sends an Flow Control message back to the client, indicating that how, when, & How much the data should be send by the Transmitter ECU.
  7. The client software receives the acknowledgement message (FC message) and can continue with its operations by using the Consecutive Frames until the next command from Receiver ECU or as per the Flow Control data or information provided by the Transmitter ECU.

Error Handling in CAN-TP (ISO 15765)

In CAN-TP Protocol, Both the transmitter and receiver monitors the data transmission with a timer. The receiver monitors the time required by the transmitter for sending Consecutive Frames. If this takes a long time, the transmission is aborted and data already transmitted are discarded. Similarly, the transmitter monitors the time for the receiver to send the flow control frame. Again the transmission is aborted and data already transmitted are discarded if a timeout is detected. The maximum waiting time for a frame is one second.

Generally, In addition to timing errors, errors in the message structure must be recognized. If an erroneous PDU is detected the frame is ignored or if a transmission is in progress, canceled it. Possible errors in the message structure are incorrect sequence numbers in consecutive frames, invalid message length, and invalid flow status in the flow control frames or an invalid PDU type. Unexpectedly arriving frames are always ignored, with the exception of a single frame and physically addressed the first frame. Functionally addressed first frame, consecutive frame, and flow control are always ignored.

How to send 100 bytes of data using CAN-TP Protocol?

I hope after learning this tutorial, you will be able to write this answer in below comments. Thank you everyone for giving your time to learn step by step about the CAN-TP Tutorial.

'경로 및 정보' 카테고리의 다른 글

임베디드 레시피 Debug  (0) 2023.07.18
Content-Type 이란  (0) 2023.06.27
source insight 메뉴얼 등  (0) 2023.06.20
ELF 파일 포맷  (0) 2023.05.30
솔라나 스마트 컨트랙트 만들어보기  (1) 2023.05.08
블로그 이미지

wtdsoul

,

https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F26733938535395B210%3Fdownload&usg=AOvVaw2gAmnrypmmfMweGgHroCvX&opi=89978449 

 

https://www.google.com/url?cad=rja&cd=&esrc=s&opi=89978449&q=&rct=j&sa=t&source=web&uact=8&url=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F26733938535395B210%3Fdownload&usg=AOvVaw2gAmnrypmmfMweGgHroCvX&ved=2ahUKEwiijO2BydD_AhVsh1YBHdrxAWAQFnoECCsQAQ

 

www.google.com

 

 

'경로 및 정보' 카테고리의 다른 글

Content-Type 이란  (0) 2023.06.27
CAN TP Protocol  (0) 2023.06.22
ELF 파일 포맷  (0) 2023.05.30
솔라나 스마트 컨트랙트 만들어보기  (1) 2023.05.08
Ubuntu 20.04 ARM Cross Compile 하기  (0) 2023.05.01
블로그 이미지

wtdsoul

,

ELF 포맷(The ELF Format) (tistory.com)

 

ELF 포맷(The ELF Format)

Executable HeaderSection HeadersSectionsProgram HeadersLazy Binding참고 및 인용리눅스 기반 시스템의 기본 바이너리 형식인 ELF(Executable and Linkable Format)을 살펴보자.ELF는 실행 가능한 바이너리 파일, 목적 파일,

rond-o.tistory.com

 

리눅스 기반 시스템의 기본 바이너리 형식인 ELF(Executable and Linkable Format)을 살펴보자.

ELF는 실행 가능한 바이너리 파일, 목적 파일, 공유 라이브러리, 코어 덤프 등에서 사용되는 형식이다. 여기서는 64비트의 실행 가능한 바이너리 파일을 중심으로 살펴보겠다.

  • A 64-bit ELF binary at a glance

ELF 바이너리는 ELF 파일 헤더, 프로그램 헤더, 그리고 섹션들과 섹션 헤더들로 이루어져 있다.

Executable Header

모든 ELF 파일은 executable header로 시작하며, /usr/include/elf.h에서 확인할 수 있다.

  • /usr/include/elf.h의 Elf64_Ehdr 정의
typedef struct {
  unsigned char	e_ident[EI_NIDENT];  /* Magic number and other info */
  Elf64_Half	e_type;         /* Object file type */
  Elf64_Half	e_machine;      /* Architecture */
  Elf64_Word	e_version;      /* Object file version */
  Elf64_Addr	e_entry;        /* Entry point virtual address */
  Elf64_Off	  e_phoff;        /* Program header table file offset */
  Elf64_Off	  e_shoff;        /* Section header table file offset */
  Elf64_Word	e_flags;        /* Processor-specific flags */
  Elf64_Half	e_ehsize;       /* ELF header size in bytes */
  Elf64_Half	e_phentsize;    /* Program header table entry size */
  Elf64_Half	e_phnum;        /* Program header table entry count */
  Elf64_Half	e_shentsize;    /* Section header table entry size */
  Elf64_Half	e_shnum;        /* Section header table entry count */
  Elf64_Half	e_shstrndx;     /* Section header string table index */
} Elf64_Ehdr;

Elf64_Ehdr 구조체의 각 원소들의 자료형인 Elf64_Half, Elf64_Word 등은 uint16_t와 uint32_t다.

  • ELF 파일 헤더의 각 필드




    •  
    •  
    •  
    •  

    •  

    •  

    •  
    •  
    •  
    •  
    •  
    •  

Section Headers

ELF 바이너리의 코드와 데이터는 섹션(section)으로 나누어져 있다.

섹션의 구조는 각 섹션의 내용이 구성된 방식에 따라 다른데, 각 섹션 헤더(section header)에서 그 속성을 찾을 수 있다. 바이너리 내부의 모든 섹션에 대한 헤더 정보는 섹션 헤더 테이블(section header table)에서 찾을 수 있다.

섹션은 링커가 바이너리를 해석할 때 편리한 단위로 나눈 것이다.

링킹이 수반되지 않은 경우에는 섹션 헤더 테이블이 필요하지 않다. 만약 섹션 헤더 테이블 정보가 없다면 e_shoff 필드는 0이다.

바이너리를 실행할 때 바이너리 내부의 코드와 데이터를 세그먼트(segment)라는 논리적인 영역으로 구분한다.

세그먼트는 링크 시 사용되는 섹션과는 달리 실행 시점에 사용된다.

  • /usr/include/elf.h의 Elf64_Shdr 구조체 정의
typedef struct {
  Elf64_Word	sh_name;        /* Section name (string tbl index) */
  Elf64_Word	sh_type;        /* Section type */
  Elf64_Xword	sh_flags;       /* Section flags */
  Elf64_Addr	sh_addr;        /* Section virtual addr at execution */
  Elf64_Off	sh_offset;        /* Section file offset */
  Elf64_Xword	sh_size;        /* Section size in bytes */
  Elf64_Word	sh_link;        /* Link to another section */
  Elf64_Word	sh_info;        /* Additional section information */
  Elf64_Xword	sh_addralign;   /* Section alignment */
  Elf64_Xword	sh_entsize;     /* Entry size if section holds table */
} Elf64_Shdr;
  • 섹션 헤더의 각 필드
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  

Sections

GNU/Linux 시스템의 ELF 파일들은 대부분 표준적인 섹션 구성으로 이루어져 있다.

$ readelf --sections --wide a.out

There are 29 section headers, starting at offset 0x1168:

Section Headers:
  [Nr] Name           Type      Address          Off    Size   ES Flg Lk Inf Al
  [ 0]                NULL      0000000000000000 000000 000000 00      0   0  0
  [ 1] .interp        PROGBITS  0000000000400238 000238 00001c 00   A  0   0  1
  [ 2] .note.ABI-tag  NOTE      0000000000400254 000254 000020 00   A  0   0  4
  [ 3] .note.gnu.build-id NOTE     0000000000400274 000274 000024 00   A  0   0  4
  [ 4] .gnu.hash      GNU_HASH  0000000000400298 000298 00001c 00   A  5   0  8
  [ 5] .dynsym        DYNSYM    00000000004002b8 0002b8 000060 18   A  6   1  8
  [ 6] .dynstr        STRTAB    0000000000400318 000318 00003d 00   A  0   0  1
  [ 7] .gnu.version   VERSYM    0000000000400356 000356 000008 02   A  5   0  2
  [ 8] .gnu.version_r VERNEED   0000000000400360 000360 000020 00   A  6   1  8
  [ 9] .rela.dyn      RELA      0000000000400380 000380 000018 18   A  5   0  8
  [10] .rela.plt      RELA      0000000000400398 000398 000030 18  AI  5  24  8
  [11] .init          PROGBITS  00000000004003c8 0003c8 00001a 00  AX  0   0  4
  [12] .plt           PROGBITS  00000000004003f0 0003f0 000030 10  AX  0   0 16
  [13] .plt.got       PROGBITS  0000000000400420 000420 000008 00  AX  0   0  8
  [14] .text          PROGBITS  0000000000400430 000430 000192 00  AX  0   0 16
  [15] .fini          PROGBITS  00000000004005c4 0005c4 000009 00  AX  0   0  4
  [16] .rodata        PROGBITS  00000000004005d0 0005d0 000012 00   A  0   0  4
  [17] .eh_frame_hdr  PROGBITS  00000000004005e4 0005e4 000034 00   A  0   0  4
  [18] .eh_frame      PROGBITS  0000000000400618 000618 0000f4 00   A  0   0  8
  [19] .init_array    INIT_ARRAY0000000000600e10 000e10 000008 00  WA  0   0  8
  [20] .fini_array    FINI_ARRAY0000000000600e18 000e18 000008 00  WA  0   0  8
  [21] .jcr           PROGBITS  0000000000600e20 000e20 000008 00  WA  0   0  8
  [22] .dynamic       DYNAMIC   0000000000600e28 000e28 0001d0 10  WA  6   0  8
  [23] .got           PROGBITS  0000000000600ff8 000ff8 000008 08  WA  0   0  8
  [24] .got.plt       PROGBITS  0000000000601000 001000 000028 08  WA  0   0  8
  [25] .data          PROGBITS  0000000000601028 001028 000010 00  WA  0   0  8
  [26] .bss           NOBITS    0000000000601038 001038 000008 00  WA  0   0  1
  [27] .comment       PROGBITS  0000000000000000 001038 000034 01  MS  0   0  1
  [28] .shstrtab      STRTAB    0000000000000000 00106c 0000fc 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)
  • .init 섹션에는 초기화에 필요한 실행 코드가 포함된다. 운영체제의 제어권이 바이너리의 메인 엔트리로 넘어가면 이 섹션의 코드부터 실행된다.
  • .fini 섹션은 메인 프로그램이 완료된 후에 실행된다. .init과 반대로 소멸자 역할을 한다.
  • .init_array 섹션은 일종의 생성자로 사용할 함수에 대한 포인터 배열이 포함된다. 각 함수는 메인 함수가 호출되기 전에 초기화되며 차례로 호출된다. .init_array는 데이터 섹션으로 사용자 정의 생성자에 대한 포인터를 포함해 원하는 만큼 함수 포인터를 포함할 수 있다.
  • .fini_array 섹션은 소멸자에 대한 포인터 배열이 포함된다. .init_array와 유사하다. 이전 버전의 gcc로 생성한 바이너리는 .ctors와 .dtors라고 부른다.
  • .text 섹션에는 메인 함수 코드가 존재한다. 사용자 정의 코드를 포함하기 때문에 SHT_PROGBITS라는 타입으로 설정되어 있다. 또한 실행 가능하지만 쓰기는 불가능해 섹션 플래그는 AX다. _start, register_tm_clones, frame_dummy와 같은 여러 표준 함수가 포함된다.
  • .rodata 섹션에는 상숫값과 같은 읽기 전용 데이터가 저장된다.
  • .data 섹션은 초기화된 변수의 기본값이 저장된다. 이 값은 변경되어야 하므로 쓰기 가능한 영역이다.
  • .bss 섹션은 초기화되지 않은 변수들을 위해 예약된 공간이다. BSS는 심벌에 의해 시작되는 블록 영역(Block Strarted by Symbol)이라는 의미로, (심벌) 변수들이 저장될 메모리 블록으로 사용한다.
  • .rel.*와 .rela.* 형식의 섹션들은 재배치 과정에서 링커가 활용할 정보를 담고 있다. 모두 SHT_RELA 타입이며 재배치 항목들을 기재한 테이블이다. 테이블의 각 항목은 재배치가 적용돼야 하는 주소와 해당 주소에 연결해야 하는 정보를 저장한다.동적 링킹 단계에서 수행할 동적 재배치만 남아 있다. 다음은 동적 링킹의 가장 일반적인 두 타입이다.
  • GLOB_DAT(global data): 이 재배치는 재배치는 데이터 심벌의 주소를 계산하고 .got의 오프셋에 연결하는 데 사용된다. 오프셋이 .got 섹션의 시작 주소를 나타낸다.이 엔트리의 오프셋은 해당 함수에서 간접 점프하는 점프 슬롯의 주소(rip로부터의 상대 주소로 계산)다.
  • JUMP_SLO(jump slots): .got.plt 섹션에 오프셋이 있으며 라이브러리 함수의 주소가 연결될 수있는 슬롯을 나타냅니다. 엔트리는 점프 슬롯(jump slot)이라고 부른다.
  • .dynamic 섹션은 바이너리가 로드될 때 운영체제와 동적 링커에게 일종의 road map을 제시하는 역할을 한다. 일명 태그(tag)라고 하는 Elf64_dyn 구조의 테이블을 포함한다. 태그는 번호로 구분한다.DT_NEEDED 태그는 바이너리와 의존성 관계를 가진 정보를 동적 링커에게 알려준다.게다가 동적 링커의 수행에 필요한 중요한 정보들을 가리키는 역할을 하기도 한다. 예를 들어 동적 문자열 테이블(DT_STRTAB), 동적 심벌 테이블(DT_SYMTAB), .got.plt 섹션(DT_PLTGOT), 동적 재배치 섹션(DT_RELA) 등.
  • DT_VERNEED와 DT_VERNEEDNUM 태그는 버전 의존성 테이블(version dependency table)의 시작 주소와 엔트리 수를 지정한다.
  • .shstrtab 섹션은 섹션의 이름을 포함하는 문자열 배열이다. 각 이름들을 숫자로 인덱스가 매겨져 있다.
  • .symtab 섹션에는 Elf64_Sym 구조체의 테이블인 심벌 테이블이 포함되어 있다. 각 심벌 테이블은 심벌명을 함수나 변수와 같이 코드나 데이터와 연관시킨다.
  • .strtab 섹션에는 심벌 이름을 포함한 실제 문자열들이 위치한다. 이 문자열들 Elf64_Sym 테이블과 연결된다. 스트립 된 바이너리에는 .symtab과 .strtab 테이블은 전부 삭제된다.
  • .dynsym 섹션과 .dynstr 섹션은 동적 링킹에 필요한 심벌과 문자열 정보를 담고 있다는 점을 제외하면 .symtab이나 .strtab와 유사하다.정적 심벌 테이블은 섹션 타입이 SHT_SYMTAB이고 동적 심벌 테이블은 SHT_DYNSYM 타입이다.

Program Headers

프로그램 헤더 테이블은 바이너리를 세그먼트의 관점에서 볼 수 있게 해준다.

바이너리를 섹션 관점에서 보는 것은 정적 링킹의 목적만으로 한정하는 것이다.

세그먼트의 관점으로 본다는 것은 운영체제와 동적 링킹 과정을 통해 바이너리가 프로세스의 형태가 되고, 그와 관련된 코드와 데이터들을 어떻게 처리할 것인지를 다룬다는 의미다.

  • /usr/include/elf.h의 Elf64_Phdr 정의
typedef struct elf64_phdr {
  Elf64_Word p_type;      /* Segment type */
  Elf64_Word p_flags;     /* Segment flags */
  Elf64_Off p_offset;     /* Segment file offset */
  Elf64_Addr p_vaddr;     /* Segment virtual address */
  Elf64_Addr p_paddr;     /* Segment physical address */
  Elf64_Xword p_filesz;   /* Segment size in file */
  Elf64_Xword p_memsz;    /* Segment size in memory */
  Elf64_Xword p_align;    /* Segment alignment, file & memory */
} Elf64_Phdr;
  • readelf로 프로그램 헤더 테이블 확인
$ readelf --wide --segments a.out

Elf file type is EXEC (Executable file)
Entry point 0x400430
There are 9 program headers, starting at offset 64

Program Headers:
  Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
  PHDR           0x000040 0x0000000000400040 0x0000000000400040 0x0001f8 0x0001f8 R E 0x8
  INTERP         0x000238 0x0000000000400238 0x0000000000400238 0x00001c 0x00001c R   0x1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x000000 0x0000000000400000 0x0000000000400000 0x00070c 0x00070c R E 0x200000
  LOAD           0x000e10 0x0000000000600e10 0x0000000000600e10 0x000228 0x000230 RW  0x200000
  DYNAMIC        0x000e28 0x0000000000600e28 0x0000000000600e28 0x0001d0 0x0001d0 RW  0x8
  NOTE           0x000254 0x0000000000400254 0x0000000000400254 0x000044 0x000044 R   0x4
  GNU_EH_FRAME   0x0005e4 0x00000000004005e4 0x00000000004005e4 0x000034 0x000034 R   0x4
  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW  0x10
  GNU_RELRO      0x000e10 0x0000000000600e10 0x0000000000600e10 0x0001f0 0x0001f0 R   0x1

➊ Section to Segment mapping:
  Segment Sections...
   00
   01     .interp
   02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame
   03     .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss
   04     .dynamic
   05     .note.ABI-tag .note.gnu.build-id
   06     .eh_frame_hdr
   07
   08     .init_array .fini_array .jcr .dynamic .got

➊에서 섹션과 세그먼트의 매핑을 확인할 수 있다. 이를 통해 세그먼트는 여러 개(0개 이상)의 섹션들을 묶은 형태라는 것을 알 수 있다.


Lazy Binding

바이너리가 프로세스의 형태로 메모리에 로드될 때 동적 링커에 의해 최종적인 재배치가 이루어진다.

예를 들어, 공유 라이브러리의 함수를 호출할 때 컴파일 시에는 해당 주솟값을 미확정인 상태로 두고, 실행 시점에 참조해 로드 한다. 특히 대부분의 재배치 작업은 첫 번째 참조가 이루어지는 시점에 수행되는 일명 지연 바인딩(lazy binding) 기법을 사용한다.

지연 바인딩은 바이너리가 실행되는 시점에서 실제로 호출하는 함수만 재배치하도록 해준다.

리눅스 ELF 바이너리의 지연 바인딩은 PLT(Procedure Linkage Table)와 GOT(Global Offset Table) 섹션을 통해 구현된다.

ELF 바이너리는 .got.plt라는 또 하나의 GOT 섹션을 이용하며, 이는 .plt 섹션과의 연동을 위한 목적이다. 결국 .got.plt 섹션은 보통 .got 섹션과 유사하며 편의상 둘이 같다고 가정하자.

아래 그림은 PLT와 GOT를 이용한 지연 바인딩 과정을 그림으로 나타낸 것이다.

  • Calling a shared library function via the PLT

.plt 영역은 실행 가능한 코드가 담겨 있는 코드 섹션이며, .got.plt는 데이터 섹션이다.

.plt 섹션을 디스어셈블해 살펴보자.

$ objdump -M intel --section .plt -d a.out

a.out:     file format elf64-x86-64

Disassembly of section .plt:

➊ 00000000004003f0 <puts@plt-0x10>:
  4003f0:	ff 35 12 0c 20 00    	push   QWORD PTR [rip+0x200c12]        # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
  4003f6:	ff 25 14 0c 20 00    	jmp    QWORD PTR [rip+0x200c14]        # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
  4003fc:	0f 1f 40 00          	nop    DWORD PTR [rax+0x0]

➋ 0000000000400400 <puts@plt>:
  400400:	ff 25 12 0c 20 00    	jmp    QWORD PTR [rip+0x200c12]        # 601018 <_GLOBAL_OFFSET_TABLE_+0x18>
  400406:	68 00 00 00 00       	push   0x0 ➌
  40040b:	e9 e0 ff ff ff       	jmp    4003f0 <_init+0x28>

➍ 0000000000400410 <__libc_start_main@plt>:
  400410:	ff 25 0a 0c 20 00    	jmp    QWORD PTR [rip+0x200c0a]        # 601020 <_GLOBAL_OFFSET_TABLE_+0x20>
  400416:	68 01 00 00 00       	push   0x1 ➎
  40041b:	e9 d0 ff ff ff       	jmp    4003f0 <_init+0x28>

PLT는 <puts@plt-0x10>:와 같이 구성되어 있다. 다음으로 일련의 함수들이 나타나며 모두 라이브러리 함수 각각에 대한 것이고 스택에 값이 1씩 증가되며 push 한다.

  • PLT를 사용해 동적으로 라이브러리 함수 참조

라이브러리 함수 중 하나인 puts 함수 호출할 때 PLT 구조를 사용해 puts@plt와 같이 호출할 수 있다➊.

PLT 구문은 간접 점프 명령으로 시작하며, .got.plt 섹션에 저장된 주소로 점프한다➋. 지연 바인딩 전에는 이 주소가 puts@plt의 다음 명령어 주소를 가리키고 있다.

스택에 push 하는 값은 PLT 구문에서 함수들을 식별하기 위한 번호다➌.

다음 명령을 통해 default stub으로 이동한다➍.

default stub은 GOT에서 가져온 또 다른 식별자를 스택에 push하고, 동적 링커 부분으로 점프(GOT로 다시 간접 점프)한다➎.

PLT 구문에 의해 스택에 push된 이 식별 정보를 통해 동적 링커는 puts 함수 주소를 찾을 수 있음을 확인하고, puts 함수를 대신 호출한다. 동적 링커는 puts 함수 주소를 찾고 puts@plt와 관련된 GOT 테이블의 항목에 해당 함수의 주소를 연결(기록)한다.

이후 puts@plt를 호출하면 GOT 테이블 내부에는 패치를 통해 실제 puts 함수의 주소가 저장되어 있으므로 PLT 구문으로 점프될 때 (앞서 다룬) 동적 링커 작업을 반복하지 않고 즉시 puts 함수로 점프한다➏.

  • GOT를 사용하는 이유

PLT 코드에 라이브러리 주소를 바로 쓰지 않고 GOT와 함께 사용하는 이유는 보안상의 문제 때문이다. 바이너리에 취약점이 존재하는 경우 공격자가 PLT overwrite를 이용해 exploit 할 수 있기 때문이다. 물론 GOT 내부의 주소를 변경해 공격하는 GOT overwrite 기법도 발표되긴 하였으나 난이도가 더 높다.

또 다른 이유는 공유 라이브러리의 코드 공유성과 관련이 있다.

  • PLT GOT 동작 과정 분석

https://rond-o.tistory.com/216?category=844537

블로그 이미지

wtdsoul

,

https://bakjuna.tistory.com/135?category=816799 

 

[solana] 솔라나 스마트 컨트랙트 만들어보기

수정: 실제로 현업에서 작동하는 프로그램을 제작하시려면 https://bakjuna.tistory.com/143 여길 참조해주세요! 이 글에선 솔라나 네이티브 코드로 제작하는 법을 설명하고 있습니다. 1. 들어가며 솔라

bakjuna.tistory.com

 

수정: 실제로 현업에서 작동하는 프로그램을 제작하시려면 https://bakjuna.tistory.com/143 여길 참조해주세요! 이 글에선 솔라나 네이티브 코드로 제작하는 법을 설명하고 있습니다.

1. 들어가며

솔라나는 최근 아주 각광받는 레이어1 코인입니다. 일단 gas fee가 이더리움에 비해 압도적으로 저렴하고, PoH라는 새로운 접근 방식을 통해 초당 트랜잭션 수도 아주 뛰어나 무려 초당 5만 개에 달하는 트랜잭션을 처리할 수 있습니다. 기존 비트코인, 이더리움의 초당 10여 개에 불과한 트랜잭션 수와 비교하면 아주 장족의 발전이죠.

 

트랜잭션의 gas fee도 현재 5000Lamport (0.000000001SOL) 에 불과해 20만원이 1솔라나라고 가정하면 트랜잭션당 소모되는 gas fee가 10원에 불과합니다. 해외 송금 해보신 분들은 아실거에요. 이 수치가 얼마나 대단한 것인지... 순식간에 이체되고, 아주 적은 비용으로 송금이 가능합니다.

 

솔라나는 당연히 최신 기술인 NFT 민팅과 Smart Contract, Defi 등을 지원합니다. 이번 글에선, 이 모든 기술들의 근간이 될 스마트 컨트랙트를 빌딩하는 방법에 대해서 알아보려고 합니다.

 

2. 스마트 컨트랙트란?

사실 많은 분들이 스마트 컨트랙트가 뭔지 구체적으로 알지 못하고 계시지만, 개념은 엄청 간단합니다. 그냥 자판기를 생각하시면 됩니다. 내가 어떤 일을 하면 어떤 결과가 나올지 미리 정해진 스크립트에 따라 수행되는 것이죠! 인터넷에 수행될 코드를 미리 업로드시켜놓고, 수행 조건이 만족되면 해당 코드가 자동으로 실행되고 연산됩니다. 이게 답니다!

 

그럼 이게 왜 혁신적이냐고요? 왜냐면, 기존에는 다들 '신뢰'에 기초해서 하던 작업들을 이젠 딱히 상대를 신뢰하지 않아도 되거든요. 예를 들면 제가 어떤 게임을 하고 이겼을 때 1000원을 받는다고 해봅시다. 종전에는 이 게임을 하고 1000원을 받는 행위 자체를 해당 사이트를 '신뢰'하지 않으면 할 수 없었습니다. 게임 사이트가 나에게 돈을 줄지, 주지 않을지 어떻게 아나요?

 

하지만 블록체인 스마트 컨트랙트는 다릅니다. 스마트 컨트랙트 온체인에서 일어나는 모든 일들은 스크립트를 읽을 수 있는 능력만 있다면 게임사이트가 특정 조건을 만족하면 자동으로 나에게 돈을 준다는 사실을 알 수 있습니다. 그렇지 않다면, 교묘하게 그렇지 않은 코드를 삽입했다는 사실도 알아낼 수 있죠. 현재는 일부 프로그래머만이 해당 사실을 검증할 수 있겠습니다만, 적어도 모두에게 공개되어있는 코드에 대놓고 스캠 코드를 삽입할 수는 없겠죠?

 

스마트 컨트랙트에 대한 대략적인 설명입니다. 먼저 프로그램된 컨트랙트 (계약)을 올려놓고, 이벤트가 진행이 되면 해당 이벤트가 실행이 됩니다. 그 실행 내역에 따라서 토큰 (뭐... 돈이죠) 이 오가는 것을 보증할 수 있습니다.

 

3. 스마트 컨트랙트 예제 살펴보기

3.1) 설치

 

솔라나 스마트 컨트랙트 예제를 살펴봅시다. 솔라나는 스마트 컨트랙트는 러스트로 짜여져 있고, 소통은 node로 할 수 있습니다.

 

 

  git clone git@github.com:solana-labs/example-helloworld.git
  sh -c "$(curl -sSfL https://release.solana.com/v1.9.1/install)"

 

이 프로젝트를 받아봅시다. 솔라나의 예제 프로젝트에요. 그리고, 솔라나도 한번 깔아봅시다. 두 번째 줄이 솔라나 CLI를 설치하는 구문입니다.

 

설치하고 나서 한번 살펴볼까요?

 

 

  solana config set --url https://api.devnet.solana.com

 

솔라나 데브넷으로 설정해보면, 다음과 같은 설정 완료창이 뜨는 것을 알 수 있습니다.

 

 

solana config 파일 저장 위치와, keypair 위치가 뜹니다. 제 솔라나 월렛 키페어가 만들어져있기 때문에 뜨는 것이죠. 만약 keypair가 뜨지 않는다면, 생성해주셔야 합니다.

 

 

 

  solana-keygen new

 

솔라나 키를 이 명령어로 생성해줄 수 있습니다.

 

3.2) 스마트 컨트랙트

솔라나는 러스트를 스마트 컨트랙트 언어로 사용한다고 했습니다. 러스트 기본 언어 문법은 배워두는게 좋습니다. 모른다고 하더라도 잠시 작성할 수는 있겠지만, 어쨌든 기초적인 것들은 알아두는게 좋으니까요.

 

solana-helloworld 프로젝트를 살펴보시면 program-rust/src/lib.rs 파일이 존재합니다. 이게 러스트 파일인데요, 이 파일이 컴파일되면 솔라나 스마트 컨트랙트가 됩니다. 한번 해당 파일을 살펴봅시다.

 

 

 

  use borsh::{BorshDeserialize, BorshSerialize};
  use solana_program::{
  account_info::{next_account_info, AccountInfo},
  entrypoint,
  entrypoint::ProgramResult,
  msg,
  program_error::ProgramError,
  pubkey::Pubkey,
  };
   
  /// Define the type of state stored in accounts
  #[derive(BorshSerialize, BorshDeserialize, Debug)]
  pub struct GreetingAccount {
  /// number of greetings
  pub counter: u32,
  }
   
  // Declare and export the program's entrypoint
  entrypoint!(process_instruction);
   
  // Program entrypoint's implementation
  pub fn process_instruction(
  program_id: &Pubkey, // Public key of the account the hello world program was loaded into
  accounts: &[AccountInfo], // The account to say hello to
  instruction_data: &[u8], // Ignored, all helloworld instructions are hellos
  ) -> ProgramResult {
  msg!("Hello World Rust program entrypoint");
   
  // Iterating accounts is safer then indexing
  let accounts_iter = &mut accounts.iter();
   
  // Get the account to say hello to
  let account = next_account_info(accounts_iter)?;
   
  // The account must be owned by the program in order to modify its data
  if account.owner != program_id {
  msg!("Greeted account does not have the correct program id");
  return Err(ProgramError::IncorrectProgramId);
  }
   
  // Increment and store the number of times the account has been greeted
  let mut greeting_account = GreetingAccount::try_from_slice(&account.data.borrow())?;
  let num: u32 = instruction_data[0] as u32;
  greeting_account.counter += num;
  greeting_account.serialize(&mut &mut account.data.borrow_mut()[..])?;
   
  msg!("Greeted {} time(s)!", greeting_account.counter);
   
  Ok(())
  }
   
  // Sanity tests
  #[cfg(test)]
  mod test {
  use super::*;
  use solana_program::clock::Epoch;
  use std::mem;
   
  #[test]
  fn test_sanity() {
  let program_id = Pubkey::default();
  let key = Pubkey::default();
  let mut lamports = 0;
  let mut data = vec![0; mem::size_of::<u32>()];
  let owner = Pubkey::default();
  let account = AccountInfo::new(
  &key,
  false,
  true,
  &mut lamports,
  &mut data,
  &owner,
  false,
  Epoch::default(),
  );
  let instruction_data: Vec<u8> = Vec::new();
   
  let accounts = vec![account];
   
  assert_eq!(
  GreetingAccount::try_from_slice(&accounts[0].data.borrow())
  .unwrap()
  .counter,
  0
  );
  process_instruction(&program_id, &accounts, &instruction_data).unwrap();
  assert_eq!(
  GreetingAccount::try_from_slice(&accounts[0].data.borrow())
  .unwrap()
  .counter,
  1
  );
  process_instruction(&program_id, &accounts, &instruction_data).unwrap();
  assert_eq!(
  GreetingAccount::try_from_slice(&accounts[0].data.borrow())
  .unwrap()
  .counter,
  2
  );
  }
  }
view rawlib.rs hosted with ❤ by GitHub

 

조금 긴데 사실 간단합니다. 지금 단계에서 알아야 할 내용은 얼마 없거든요.

 

borsh는 Binary Object Representation Serializer for Hashing 의 약어입니다. 바이너리로 풀어진 오브젝트를 시리얼라이징하거나 디시리얼라이징할 때 쓰이는 일종의 라이브러리라고 생각하시면 됩니다. node에도 동일하게 존재하는 라이브러리입니다.

 

entrypoint!() 에서 실제 스마트 컨트랙트가 선언되는데, 이 안 내용을 한번 살펴보시죠.

 

account.iter()는 map과 비슷한겁니다. account를 iterable하게 돌려서 account를 하나씩 살펴본다는 의미입니다. 그래서 next_account_info로 account를 가지고 온 후, 해당 account_info에 데이터를 set해줄 준비를 할 겁니다. 데이터를 저장해야 그 데이터로 무언가 로직을 돌릴테니, 이번 예제에선 데이터를 저장하는 로직만 해볼거에요. 그러니까... UPDATE, INSERT, SELECT 세 기능을 구현해 볼 겁니다.

 

 

  let mut greeting_account = GreetingAccount::try_from_slice(&account.data.borrow())?;
  let num: u32 = instruction_data[0] as u32;
  greeting_account.counter += num;
  greeting_account.serialize(&mut &mut account.data.borrow_mut()[..])?;
   
  msg!("Greeted {} time(s)!", greeting_account.counter);
   
  Ok(())

 

여기가 핵심 로직인데요, account의 data를 가지고 와서, instruction_data를 해당 data에 set 해주는 부분입니다. instruction_data는 우리가 직접 넣어주는 데이터가 될 거에요. 그 다음 다시 저장해주고 Ok() 해주면, 해당 노드 블럭에 스마트 컨트랙트 내용이 저장됩니다.

 

여기까지 간단하게 러스트 코드를 살펴봤습니다. 너무 간단해서 볼 내용도 별로 없죠?

 

3.3) 데이터 set 하고 불러오기

부차적인 내용은 다 빼고, 하나하나 차근차근 빠르게 보겠습니다.

 

1] 커넥션 맺기

 

  const rpcUrl = await getRpcUrl();
  connection = new Connection(rpcUrl, 'confirmed');
  const version = await connection.getVersion();
  console.log('Connection to cluster established:', rpcUrl, version);

 

rpcUrl은 devNet 주소입니다. jsonRPC로 통신할 url이 어딘지 fetch해오거나 아니면 선언해주는 곳입니다. 그 이후, confirmed 블록만 가져와서 커넥션을 맺어주는걸 new Connection을 통해 진행합니다.

 

 

 

  let fees = 0;
  if (!payer) {
  const {feeCalculator} = await connection.getRecentBlockhash();
   
  // Calculate the cost to fund the greeter account
  fees += await connection.getMinimumBalanceForRentExemption(GREETING_SIZE);
   
  // Calculate the cost of sending transactions
  fees += feeCalculator.lamportsPerSignature * 100; // wag
   
  payer = await getPayer();
  }
   
  let lamports = await connection.getBalance(payer.publicKey);
  if (lamports < fees) {
  // If current balance is not enough to pay for fees, request an airdrop
  const sig = await connection.requestAirdrop(
  payer.publicKey,
  fees - lamports,
  );
  await connection.confirmTransaction(sig);
  lamports = await connection.getBalance(payer.publicKey);
  }
   
  console.log(
  'Using account',
  payer.publicKey.toBase58(),
  'containing',
  lamports / LAMPORTS_PER_SOL,
  'SOL to pay for fees',
  );
view rawgetPayer.ts hosted with ❤ by GitHub

 

payer를 정해줍니다. 이번엔 devnet이기 때문에, payer는 우리죠. 따라서 만약 부족하면 데브넷에서 에어드롭을 받는 로직까지 추가합니다. 실제 디앱에선 이렇게 하면 안되고 에어드롭 받는 부분은 제외해야합니다. 만약 잔액이 부족하면 그냥 에러를 뱉어야죠.

 

 

 

  try {
  const programKeypair = await createKeypairFromFile(PROGRAM_KEYPAIR_PATH);
  programId = programKeypair.publicKey;
  } catch (err) {
  const errMsg = (err as Error).message;
  throw new Error(
  `Failed to read program keypair at '${PROGRAM_KEYPAIR_PATH}' due to error: ${errMsg}. Program may need to be deployed with \`solana program deploy dist/program/helloworld.so\``,
  );
  }

 

프로그램 에러가 있는지, 프로그램이 있긴 한지 체크해봅니다.

 

 

 

  const instruction = new TransactionInstruction({
  keys: [{pubkey: greetedPubkey, isSigner: false, isWritable: true}],
  programId,
  data: Buffer.alloc(1, 2), // 실제 넣을 데이터
  });
   
  await sendAndConfirmTransaction(
  connection,
  new Transaction().add(instruction),
  [payer],
  );
view rawtransaction.ts hosted with ❤ by GitHub

 

이제 실제로 트랜잭션을 발생시킵니다. TransactionInstruction의 input이 아까 러스트랑 동일하죠? 맞습니다, 러스트에서 받는 인자들입니다. 따라서 data에 저렇게 넣으면 2라는 숫자가 들어갑니다. 중요한건, Buffer로 선언된 ㄹㅇ 바이너리 숫자가 들어간다는 사실입니다.

 

 

 

  const accountInfo = await connection.getAccountInfo(greetedPubkey);
  if (accountInfo === null) {
  throw 'Error: cannot find the greeted account';
  }
  const greeting = borsh.deserialize(
  GreetingSchema,
  GreetingAccount,
  accountInfo.data,
  );

 

트랜잭션이 맺어지고 난 이후 현재 데이터는 어떻게 불러올까요? getAccountInfo를 통해 가져와서, data를 불러옵니다. 그러면 우리가 set한 데이터 정보들이 나옵니다!

 

 

4. 마무리

이렇게 솔라나로 아주 간단한 스마트 컨트랙트를 만들어 봤습니다. 노드를 통해 통신할 수 있으니, 아마 노드 웹서버를 통해 통신할 수도 있겠지요.

 

이런 스마트 컨트랙트를 openDB, open Logic으로도 사용할 수 있습니다. 누구나 볼 수 있는 DB, 그리고 누구나 볼 수 있는 로직이기 때문에 절대로 속일 수 없다는 특징을 가지게 된 것이죠. 누구나 검증할 수 있게 된 것입니다. 물론, 스마트컨트랙트를 누구나 발생시킬 수 있기 때문에, 실제 구현상에선 greetedPubKey의 SEED를 적절하게 잘 숨겨서 보관하는 것도 중요합니다. 해당 seed가 곧 DB의 암호가 되는 셈이니까요.

 

이 글이 한국어 사용자의 스마트 컨트랙트 이해도 향상에 조금이나마 도움이 되었으면 좋겠습니다! 기회가 된다면 다음 글로 곧 스마트컨트랙트의 실제 활용도 한번 보여드리겠습니다.

 

추가)

사실 프로그램을 만들 때엔 anchor를 이용하시는 것이 좋습니다. 해당 글도 읽어보세요!

https://bakjuna.tistory.com/138

 

[anchor] anchor를 이용하여 solana program (smart contract) 작성하기

1. 들어가며 anchor는 solana 프로그램을 작성할 때 훨씬 더 쉽게 작성할 수 있게 도움을 주는 라이브러리입니다. 솔라나 프로그램을 네이티브로 작성하는 것보다, anchor를 이용하여 작성하는게 훨씬

bakjuna.tistory.com

 

블로그 이미지

wtdsoul

,

https://www.skyer9.pe.kr/wordpress/?p=4518 

 

Ubuntu 20.04 ARM Cross Compile 하기 – 상구리의 기술 블로그

Ubuntu 20.04 ARM Cross Compile 하기 기초지식 ARM 은 hard float(하드웨어 GPU) 가 있는 버전이 있고, 없는 버전이 있습니다. EABI(Embedded Application Binary Interface)는, ARM의 C 호출 관행이라고 이해하면 됩니다. 툴

www.skyer9.pe.kr

 

Ubuntu 20.04 ARM Cross Compile 하기

기초지식

ARM 은 hard float(하드웨어 GPU) 가 있는 버전이 있고,
없는 버전이 있습니다.

EABI(Embedded Application Binary Interface)는,
ARM의 C 호출 관행이라고 이해하면 됩니다.

툴체인 설치

개발용 필수 라이브러리 설치

 sudo apt-get install build-essentialCOPY

hard float(하드웨어 GPU) 가 있는 경우

# gnu c compiler(32bit)
sudo apt-get install gcc-arm-linux-gnueabihf
# gnu c++ compiler(32bit)
sudo apt-get install g++-arm-linux-gnueabihfCOPY

hard float(하드웨어 GPU) 가 없는 경우

# gnu c compiler(32bit)
sudo apt-get install gcc-arm-linux-gnueabi
# gnu c++ compiler(32bit)
sudo apt-get install g++-arm-linux-gnueabiCOPY

64bit 버전

# gnu c compiler
sudo apt-get install gcc-aarch64-linux-gnu
# gnu c++ compiler
sudo apt-get install g++-aarch64-linux-gnuCOPY

컴파일

Hello, World!

vi hello.cpp
--------------------------
#include <iostream>
using namespace std;

int main() {
    cout << "Hello, World!" << endl;
    return 0;
}
--------------------------

arm-linux-gnueabihf-g++ -g -o hello hello.cpp
file hello
hello: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV) ......

aarch64-linux-gnu-g++ -g -o hello hello.cpp
file hello
hello: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV) ......COPY

make

우선 알아야 할 것은 make 가 직접적으로 cross compile 을 지원하는 것이 아니라,
Makefile 에 cross compile 을 지원하도록 설정해야 하는 것입니다.

들여쓰기는 TAB 문자로만 이루어져야 합니다.
공백문자가 있는 경우 오류가 발생합니다.

AR=${CROSS_COMPILE}ar
AS=${CROSS_COMPILE}as
LD=${CROSS_COMPILE}ld
CC=$(CROSS_COMPILE)gcc
CXX=$(CROSS_COMPILE)g++
NM=${CROSS_COMPILE}nm
RANLIB=${CROSS_COMPILE}ranlib

CFLAGS=""
CPPFLAGS=""
LDFLAGS=""
LIBS=""

hello : hello.cpp
        $(CXX) -g -o hello hello.cpp

clean:
        rm helloCOPY

make 를 이용하여 컴파일하는 경우, 아래와 같이 환경설정을 추가하고 컴파일하면 됩니다.

# 32bit, GPU 없는 경우
export CROSS_COMPILE=arm-linux-gnueabi-
export ARCH=arm
# 32bit, GPU 있는 경우
export CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH=arm
# 64bit
export CROSS_COMPILE=aarch64-linux-gnu-
export ARCH=arm64

make

file hello
hello: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV) ......COPY

아래 명령을 .bashrc 에 추가해 놓으면 편합니다.

alias armmake='make -j8 ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- '
alias arm64make='make -j8 ARCH=arm CROSS_COMPILE=aarch64-linux-gnu- 'COPY

cmake

샘플로 json-c 라이브러리를 컴파일합니다.

git clone https://github.com/json-c/json-c.git
cd json-c
mkdir build
cd build
# cmake ..COPY
vi ../toolchain.arm.cmake
-------------------------------
SET(CMAKE_C_COMPILER ${CROSS_COMPILE}gcc)
SET(CMAKE_CXX_COMPILER ${CROSS_COMPILE}g++)
SET(CMAKE_LINKER ${CROSS_COMPILE}ld)
SET(CMAKE_NM ${CROSS_COMPILE}nm)
SET(CMAKE_OBJCOPY ${CROSS_COMPILE}objcopy)
SET(CMAKE_OBJDUMP ${CROSS_COMPILE}objdump)
SET(CMAKE_RANLIB ${CROSS_COMPILE}ranlib)
-------------------------------COPY
cmake -DCMAKE_TOOLCHAIN_FILE=toolchain.arm.cmake -DCROSS_COMPILE=aarch64-linux-gnu- ..

make
file libjson-c.so.5.1.0
libjson-c.so.5.1.0: ELF 64-bit LSB shared object, ARM aarch64, version ......COPY

configure

./configure --host=arm-linux-gnueabiCOPY
 
블로그 이미지

wtdsoul

,