1. 라즈베리파일 Imager 로 설치

 32bit 로 설치한다.

 

2. 라즈베리파일 설정

ssh 설정, wifi 설정

라즈베리파이 imager로 만든

마이크로 SD에 boot로 설정된 파티션으로 들어가서

$ sudo -i
# cd /run/media/fehead/boot
# touch ssh  # ssh enable
# cat > wpa_supplicant.conf
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=US

network={
	ssid="와이파이SSID"
	psk="와이파이암호"
}

^d

 

3. 라즈베리에 sd카드를 삽입후 ssh 접속

$ ssh pi@pi_ip_addrss

 username: pi
 password: raspberry

 

4. 라즈베리파이 셋팅

ssh 접속후

 $ sudo raspi-config
 
 6 Advanced Options -> A1 Expand Filesystem
 5 Localisation Options -> L1 Locale([*] ko_KR.UTF-8 UTF-8)
                        -> L2 Timezone -> Asia -> Seoul


# vim 설치
$ sudo apt remove --purge vim-tiny
$ sudo apt install vim

# 미러 사이트 수정
$ sudo vi /etc/apt/sources.list
deb http://ftp.kaist.ac.kr/raspbian/raspbian/ bullseye main contrib non-free rpi
로 변경.

# upgrade
$ sudo apt update
$ sudo apt upgrade

 

원본 : https://github.com/iamroot12CD/linux/wiki/raspberrypi2_kernel_debug

QEMU를 이용한 라즈베리파이2 커널 디버깅

QEMU 컴파일

$ mkdir -p ~/git/pi2
$ cd ~/git/pi2

$ git clone https://github.com/0xabu/qemu.git -b raspi
$ cd qemu
$ git submodule update --init dtc
  • QEMU 커널 BASE 주소 수정
    • Qemu 소스 hw/arm/boot.c 파일 수정.
#define KERNEL_LOAD_ADDR 0x00010000

이것을 아래처럼 수정.

#define KERNEL_LOAD_ADDR 0x00008000
  • compile 및 설치
$ cd ~/git/pi2/qemu

$ ./configure --target-list=arm-softmmu
$ make -j$(nproc)
$ sudo make install

라즈베리파이2용 커널 컴파일

$ mkdir ~/git/pi2
$ cd ~/git/pi2
$ git clone https://github.com/raspberrypi/tools
  • 컴파일 환경 설정 파일 만들기
$ vi env.sh
#!/bin/sh
export PATH=~/git/pi2/tools/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/bin:$PATH
export KERNEL=kernel7

export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
  • 환경 설정 적용.
$ source env.sh
  • 커널 컴파일시 추가 셋팅
$ cd ~/git/pi2/
$ git clone --depth=1 https://github.com/raspberrypi/linux

$ cd linux
$ make bcm2709_defconfig
$ make menuconfig

Kernel hacking --> Compile-time checks and compiler option --> Compile the kernel with debug info --> Enable
  • 최적화 옵션 변경
$ vim Makefile
ifdef CONFIG_CC_OPTIMIZE_FOR_SIZE
KBUILD_CFLAGS   += -Os $(call cc-disable-warning,maybe-uninitialized,)
else
KBUILD_CFLAGS   += -O2 # --> 이것을 -O1 으로 변경.. 
endif
  • 최적화 옵션을 설정 하지 않을려면 -O0으로 해야 하나 -O0으로 컴파일 하면 에러가 발생한다..(혹시 수정 방법을 아시는 분은 메일로 좀 알려주시길..)
  • 커널 빌드
$ make -j$(nproc) zImage modules dtbs

커널 디버깅

  • 컴파일 된 커널과 DTB파일 추출
$ scripts/mkknlimg arch/arm/boot/zImage ~/git/pi2/kernel7.img
$ cp arch/arm/boot/dts/bcm2709-rpi-2-b.dtb ~/git/pi2
  • QEMNU 실행 스크립트 작성.
$ vi ~/git/pi2/run_qemu.sh
#!/bin/sh
AA=$(echo $PATH|grep linux-gnueabihf |wc -l)
if [ ${AA} -eq 0 ] ; then
    export PATH=~/git/pi2/tools/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/bin:$PATH
    export KERNEL=kernel7

    export ARCH=arm
    export CROSS_COMPILE=arm-linux-gnueabihf-
fi

BOOT_CMDLINE="rw earlyprintk loglevel=8 console=ttyAMA0,115200 console=tty1 dwc_otg.lpm_enable=0 root=/dev/mmcblk0p2"
DTB_FILE="bcm2709-rpi-2-b.dtb"
KERNEL_IMG="kernel7.img"
SD_IMG="raspbian-jessie.img"

echo "target remote localhost:1234"
qemu-system-arm -s -S -M raspi2 -kernel ${KERNEL_IMG} \
    -sd ${SD_IMG} \
    -append "${BOOT_CMDLINE}" \
    -dtb ${DTB_FILE} -serial stdio &

QEMU_PID=$!
sleep 1
arm-linux-gnueabihf-gdb -ex "target remote localhost:1234" ~/git/pi2/linux/vmlinux

kill -9 ${QEMU_PID}
  • QEMU 실행 및 디버깅
$ chmod a+x run_qemu.sh

$ ./run_qemu.sh 
target remote localhost:1234
VNC server running on '::1;5900'
Reading symbols from ./vmlinux...done.
Remote debugging using localhost:1234
__vectors_start () at arch/arm/kernel/entry-armv.S:1219
1219        W(b)    vector_rst

# start_kernel에 브레이크 포인트 설정
(gdb) b start_kernel
Breakpoint 1 at 0x8079d9c0: file init/main.c, line 546.

# 디버깅 시작 continue
(gdb) c
Continuing.
Uncompressing Linux... done, booting the kernel.

Breakpoint 1, start_kernel () at init/main.c:546
546 {

# 소스보기.
(gdb) list
541     vmalloc_init();
542     ioremap_huge_init();
543 }
544 
545 asmlinkage __visible void __init start_kernel(void)
546 {
547     char *command_line;
548     char *after_dashes;
549 
550     /*
(gdb) 


Writer


Unix V6 함수 리턴과 스위칭 2(완)


그럼 실제로 UNIX V6에서 스위칭 되는 소스를 한번 봐보겠습니다


1. 데이터 영역 구조


일단 소스를 보기 전에 데이터 세그먼트 메모리 구조 부터 보기로 하겠습니다.

일단 주소는 간단한 설명을 위해 100부터 시작했음을 알려 드립니다.

UNIX V6 데이터 영역 0번지에는 user 구조체가 자리잡고 있고 그 위에 데이터 영역과 스택 영역이 자리 잡고 있습니다.

이해 하시는데 문제 없을 거라 생각하고 다음으로 넘어 가겠습니다.




2. fork() 함수와 newproc() 리턴 값 1

UNIX V6 72page 리스트 3.3 fork() 소스 13라인의 설명을 보면 부모 프로세스는 0을 리턴 받고

자식 프로세스는 1를 리턴 받는다고 나옵니다.


하지만 newproc() 함수를 보면 0을 리턴하는 소스는 있지만 눈을 씻고 봐도

1를 리턴하는 소스는 볼수가 없습니다.



 


소스 설명에는 switch() 함수가 1을 리턴해서 1을 가져 온다고 써있는데




도.대.체 어떻게 아무런 접점도 찾을수 없는 fork()함수와 switch()가

switch() 함수가 1을 리턴했는데 어떻게 fork()함수가 1을 리턴 받을까요?





3. fork() 함수와 newproc() 리턴 값 2


그럼 설명에 들어 가겠습니다.

일단 fork() 함수 13 라인과 데이터 영역 메모리 그림부터 보겠습니다.




저번에 했던 함수와 스택 구조 그림을 그대로 그려 보면서 그 과정을 보겠습니다.

fork() 함수 13라인이 실행 되고 있는 그림입니다.


R5 = 100

R6 = 99

R7 = fork() 13라인



지역변수가 여러개 혹은 없을수 있지만 신경 쓰지 않아도 되므로 지역변수들이라고 써놓았습니다.

그리고 위의 fork() 함수를 실행하고 있는 프로세스는 부모 프로세스입니다.(자식은 아직 생생도 되지 않았죠..)

여기서 newproc() 함수를 호출합니다.




4. newproc() 호출 1


자 그럼 이제 newproc를 호출 합니다.

전 게시글을 보셨으면 이해 하실수 있을겁니다.

스택에 리턴 어드레스 저장(fork함수 13라인), fork() 함수 프레임 저장(R5 = 100)

어셈블리언어로 하면(r6가 99에서 시작함)


mov r7, -(r6)    --> 스택에 리턴 어드레스(fork 함수 13라인)를 저장, 그리고 r6 = r6 -1

mov r5, -(r6)    --> 스택에 fork함수 프레임 주소(100) 저장, 그리고 r6 = r6 -1




5. newproc() 호출 2


이제 R5에 R6(97)값을 넣으면 되지요.


mov r6, r5    --> R5 = R6(97) --> R5 = 97





6. 함수 프레임및 스택 포인터 저장


자 이제 함수 프레임및 스택 포인터를 저장합니다.(newproc 함수 50라인)





savu 함수는 user 구조체에 r5, r6레지스터값을 저장하는 함수 입니다.

그림으로 보니 아무것도 아니죠? ^^





7. 프로세스 복제 1


자 이제 부모 프로세스가 자식 프로세스를 생성하는 작업을 완료하고



데이터 영역을(newproc 함수68라인) 복사합니다.






8. 프로세스 복제 2


데이터 영역 복제 완료하면 아래와 그림과 같습니다.



unix v6 PDP 11 시스템은 cpu가 하나 밖에 없으며 한번에 하나의 프로세서만 실행 시킬수 있습니다.

그러므로 부모로부터 복제된 자식 프로세스는 메모리 복제가 일어난 상태이고 실행중이 아닙니다.




9. 부모 프로세서의 리턴



부모 프로세서는 실행을 계속하여 newproc() 71라인으로 오게 되며 0을 리턴 받습니다.







10. 자식 프로세스의 실행 1


자식 프로세스는 실행중이 아니라고 했습니다.

그럼 자식 프로세스는 어디서 다시 실행될까요?


바로 switch() 함수 입니다.


일정 시간이 되거나 우선순위가 되면 자식 프로세스는 실행가능상태에서 실행상태로 변하고

실행을 재게 합니다.(switch 함수)




switch 함수 40라인 전에서 자식 프로세스가 선택되었다고 가정합니다.

40라인에서 retu 함수가 실행 됩니다.


retu 함수는 그림에 나타나 있듯이 user 구조체에 있는 R5, R6값을 cpu로 옮기는 작업을 합니다.

하지만 아직 cpu는 자식 프로세스를 완전하게 실행하고 있지 않습니다.





11. 자식 프로세스의 실행 2 - 데이터 영역 사용


아직 위그림까지는 데이터 영역을 완벽하게 사용하지 못하고 있습니다.

또한 자식 프로세스가 완벽하게 실행하고 있다고 볼수 없습니다.(아직 switch 함수 실행중)




이제 switch 41라인까지  왔습니다.

여기서 드디어 cpu가 데이터 영역을 완벽하게 자식프로세스로 바꿉니다.


하지만 아직도 소스는 switch 함수를 실행중입니다.

아직 자식 프로세스가 실행중이라고 말하기에는 좀 부족합니다.





12. 함수 리턴과 fork() 함수 복귀!!! - 스위칭 완료!!!!


무엇이 부족할까요?

fork 함수 13라인을 보면 자식 프로세스는 1를 리턴 받는다고 나옵니다.


책에서 fork() 함수가 newproc()호출하면 부모 프로세스는 0을 리턴받고

자식 프로세스는 1를 리턴 받는다고 했습니다.


자 그럼 switch 함수 48라인이 실행 되면 어떻게 될까요?




여기서 swtich 함수에서 return를 했습니다.

저번 게시물에서 봤던 함수 리턴입니다.



저번 게시물에서 봤던 그대로 따라해 보겠습니다.

그럼 아래와 그림 같이 될것입니다.




 (1). 스택에서 R5 값 100(fork 함수 프레임)을 가져온다.

 (2). 스택에서 리턴 어드레스 값을 가져온다. R7 = fork() 13라인

 (3). switch() 에서 리턴한 값 1을 가져온다.


자!!! 그럼 자식 프로세스는 이제 어디를 실행하고 있나요?


fork 함수 13라인을 실행하고 있습니다.(사실 14라인이지만 ㅎㅎ 캡쳐 실패)


이것이 스위치의 원리입니다.


참고로 위의 게시글과 같이 UNIX V6 exec 함수 14라인 (sleep 함수 호출)도 한번 해보세요.

동일하게 exec 14라인으로 되돌아 올것입니다.



그럼 이제 unix v6 스위치가 끝났습니다.



오타나 이해 되지 않으시면 댓글이나 혹은 다른 연락 가능한 곳으로 문의 주세요^^




Unix V6 함수 리턴과 스위칭 1


Unix v6 함수 리턴과 스위칭에 대해 한번 알아 보겠습니다.


저번 게시물에서 마지막에 스위칭에 대한 힌트를 마지막에 올렸습니다.


아래와 같았고 B함수에서 리턴(return) 되면 과연 어디로 리턴될 것인가를 묻고 끝냈었습니다.




그럼 오늘은 풀이를 한번 해보겠습니다.



1. 이상한 함수 리턴 문제 풀이 1

먼저 A() 함수에서  B() 함수를 호출하는 코드와 스택 구조를 한번 봐보겠습니다.





만약 위 그림이 이해 되지 않는다면 다시 첫 게시물부터 다시 보세요.

먼저 A() 함수 11라인을 보겠습니다.


여기서 r5 는 R5레지스터 라고 가정하겠습니다.

r5는 A함수 프레임 첫 스택 주소인 96를 가지고 있습니다.

s_r5 = r5(96) --> s_r5 = 96  : savu 함수와 비슷한 기능을 합니다.

R5 = 96

R6 = 94

R7 = 11 라인



2. 이상한 함수 리턴 문제 풀이 2


이제 A()함수에서 B()함수를 호출하고 17라인일때의 그림은 아래와 같습니다.




스택에 리턴 주소인 A() 함수 12라인과  A()함수 프레임 주소 96이 저장되어 있습니다.

R5 = 92

R6 = 92

R7 = B() 함수 17 line



3. 이상한 함수 리턴 문제 풀이 3



여기서 r5 = s_r5(96) 가 나옵니다.

그러면 r5 = 96 이 됩니다.


R5 = s_rt(96) -->R5 = 96  : unix v6의 retu 와 비슷한 기능을 합니다.

R6 = 92

R7 = B()함수 17 line




4. 이상한 함수 리턴 문제 풀이 4



그러면  B() 함수 18라인에서 return이 나오면 어떻게 될까요?

원래대로라면 A()함수 13라인으로 가야 맞겠지요..


하지만 위에서 R5 가 96(A함수 프레임)으로 바뀌었습니다.


이전 게시물에서 리턴을 한다면

1. 스택 포인터 R6에 R5를 넣고

2. R7(프로그램 카운터)에 리턴 어드레스(R6 기준)를 넣는다고 했습니다.


그럼 한번 그대로 한번 해보겠습니다.




5. 이상한 함수 리턴 문제 풀이  5




일단 R5에 스택에 저장되어 있는 main()함수  프레임 주소를 가져옵니다.

그리고 스택에서 R7에 리턴 주소인 main() 함수 6라인 값을 가져옵니다.

R6는 각각 1씩 줄어 98이 됩니다.


R5 = 100  --> 스택(주소 97)에서 main() 프레임 주소 100가져옴 

R6 = 98

R7 = 6 line --> 스택(주소 98)에서 리턴 어드레스(main() 6라인)를 가져옴


18라인 B()함수에서  리턴을 했는데  main()함수 6라인으로 리턴되었습니다.



6. Unix v6 스위칭 힌트


이것이 바로 Unix V6 스위칭힌트입니다.

savu 함수가 함수 프레임을 저장하는 것이고

retu 함수가 함수 프레임을 복구 시키는 것입니다.


결국 savu 는 현재 실행되고 있는 함수 위치를 저장하는 것이고

retu는 이전에 실행 되고 있는 함수 위치를 복구 시키는 것입니다.

단지 여기서는 R5, R6만 복구 하고 있다는 것입니다.


R5는 함수 프레임 시작위치이고 R6는 스택 포인터입니다.

하지만 이상하게도 R7(프로그램 카운터)는 복구 되지 않습니다.


그러나 잘 생각해 보시면 R7(프로그램 카운터) 값은 스택에 리턴 어드레스 값이  저장 되어 있다

사실을 알아둘 필요가 있습니다.


함수 프레임과 스택 프레임을 복구 시키고

C 언어에서 return를 만나게 되면 스택에서 리턴어드레스가 R7에 복구 되면서

비로소 기존에 실행 했던 함수 코드로 반환 되어 다시 실행 됩니다.


위에서는 같은 프로세스 내에서 B() 함수 프레임 위치를 A() 함수 프레임 위치로 바꾸어서 했습니다.

하지만 retu는 같은 프로세스의 R5, R6를 바꾸는것이 아니라

다른 프로세스의 R5, R6를 바꾸는것입니다.


그러므로 다른 프로세스의 다른 함수로 복구 된다는 말이 됩니다.


다음 게시물은 Unix V6 스위칭에 대해 써보겠습니다.


함수 호출과 스택 구조 네번째 글입니다.


이 게시글에서는 함수 리턴에 대해 설명하겠습니다.
이 글 끝에 보면 Unix V6에서 커널 switch 를 이해 할수 있는 힌트가 있습니다.



1. A 함수 리턴 1 - return 명령 실행





자 그럼 이어서 하겠습니다.

A 함수 13번째 라인를 실행한다고 하겠습니다.( 알아보기 쉽게 return 를 넣었습니다.)

이때 레지스터 값들은


R5 = 96 (A 함수 프레임)

R6 = 94 (스택 포인터)

R7 = 13 (code 13라인)





2. A 함수 리턴 2 - 스택 포인터 복구(R6)


A 함수가 리턴을 만났습니다.


먼저 R6 스택포인터(sp)를 R5로 바꿉니다

R5 = 96

R6 = R5(96) --> R6 = 96 

R7 = 13






3. A 함수 리턴 3 - main() 프레임 포인터 복구(R5)

이제 R6 가 가르키는 스택에서 main 함수 프레임 값을 가져와 R5에 복구 합니다.

R6 = R6(96) + 1 ; ( 으.. 그림를 살짝 잘못 캡쳐 했네요 ㅡ.ㅜ R6가 97이어야 하는데 이해좀 ㅡ.ㅜ)

R6 = 97;

R5 = 100;   -->  R5 = (R6)  R5 = (스택 97 번지의 값) (**그림 참고)








4. A 함수 리턴 4 - return address 복구(R7)


R5 = 100;

R6 = 97 + 1;

R7 = 5 라인;  --> 스택(99 주소)에서 리턴 어드레스 복구





이로서 A 함수에서 main함수로 리턴 되었습니다.


이것이 함수의 호출과 스택 구조가 되겠습니다.


설명이 좀 부실하네요 ^^




4. 이상한 함수 리턴과 커널 스위칭 힌트


자 그럼 게시글의 마지막으로 커널 스위칭에 대해 살짝 힌트를 드리겠습니다.




자 여기서 18라인을 실행하면 코드는 몇라인으로 갈까요?^^

위 코드에서 r5은 R5레지스터라 가정하겠습니다.^^


반드시 노트에 스택과 레지스터를 그려보시고

생각해 보십시요.

...

...



아 return 1; return 0; 에 대한것을 빼먹었군요.


마지막에 return 1; 를 하면

1이라는 값은 R0 에 저장됩니다.

호출한 함수에서는 호출이 끝나고 R0 레지스터 값을 반환값으로 처리합니다.





함수 호출과 스택 구조 세번째 글입니다.


저번 게시물에서


7. A 함수 종료 2 에서 아래의 빨간 글씨로 중요 하다고 써놨습니다.


7. A 함수 종료 2


이제 main 함수 5라인으로 복귀합니다.

여기서 다시 R5 = 100으로 바꾸고

R7를 5로 바꿉니다.


R5 = 100(?)

R6 = 98

R7 = 5(?)



****** 중요

여기서 의문인점이 생깁니다.

도대체 main 함수의 스택 위치100은 어디에서 왔으며(어디에 저장되어 있나?)

다음 실행라인(pc, R7)5라인은 어디에서 왔을까요?(어디에 저장되어 있나?)

이것은 다음 게시물에서 다루겠으며 어디에 저장할지 추측해 보세요.




여기서 main 함수의 스택 위치100

다음 실행라인(pc, R7)5라인 - 리턴 어드레스(return adress)

예상 하셨을텐데 스택에 저장 됩니다.


그럼 어떻게 저장될까요?

다시 그림 A() 함수를 호출할때를 보시겠니다.




1. A 함수 호출 1





여기 4라인에서 A 함수를 호출합니다.

저번 게시물에서는 R5를 98로 로 바꾸었는데요.

  R5 = R6(98) --> R5 = 98

  R6 = 98

  R7 = 4


원래는 최소 두가지 일을 더합니다.

저번 게시물때는 쉬운 설명을 위해 많은 것을 생략했었습니다.


1. 리턴 어드레스(return address)를 스택에 저장합니다. - 여기서는 메인함수 4라인이 아니라 다음에 실행할 메인함수 5라인 입니다.

2. R5(프레임포인터) 값을 스택에 저장합니다. - 여기서는 main 함수의 스택 시작값인 100

... (사실 이것 말고도 더 있습니다. 매개 변수라던가... 기타 레지스터라던가...)


그리고 다음에는 저번 게시물에 설명한  "4. A 함수 호출 9라인 - A함수 호출" 로 이동합니다.


그럼 다시 자세한 그림을 보겠습니다.




2. A 함수 호출 2 - 리턴 어드레스(return address) 값을 스택에 저장




스택에 다음 실행 위치(R7 + 1)인 5라인을 스택에 저장합니다. R7(프로그램 카운터 pc)


(R6) = R7(4) + 1 --> (98) = 5 line : 스택 주소 98에 코드 주소인 5라인을 저장

R6 = R6(98) - 1  --> R6 = 97

R7 = 4




3. A 함수 호출 3 -  R5(프레임포인터) 값을 스택에 저장



스택에 R5값(main 함수 스택 시작위치) 스택에 저장합니다.


(R6) = R5(100) --> (97) = 100 : 스택 주소 97에 R5((main 함수 스택 시작위치) 스택에 저장

R6 = R6(97) - 1  --> R6 = 96

R7 = 4





3. A 함수 호출 4 -  드디어 A함수로...




드디어 A 함수로 왔습니다.


R5 = R6(96) --> R5 = 96

R6 = 96

R7 = 9(A 함수 시작 라인)



오 드디어 이제 A 함수가 호출 되었습니다.


A 함수가 종료 될때는 위 역순으로 돌아 가게 되겠습니다.(노트에 추측해서 한번 그려보세요^^)

그럼 이제 종료하겠습니다..

..

..

..


..


다음 게시물에서 main 함수로 복귀를 보여 드리겠습니다.

함수 호출과 스택 구조 2 편입니다.


이번에는 함수를 한줄 한줄 넘어가면서 어떻게 스택이 생성되며

r5(프레임 포인터 레지스터, 인텔계열-ebp)

r6(스택포인터-sp, 인텔계열 - esp) 가 어떻게 변하는지 보겠습니다.



1. 먼저 main 함수가 시작될때 그림입니다.

R5 와 R6가 100으로 초기화 됩니다.

별다른 설명이 없어도 되겠죠?

아 그리고 코드 실행 위치가 1(라인) 이 됩니다.

코드실행 위치를 저장하는 레지스터가 R7(pc 라고도 합니다 program counter)입니다.

위에서는 R7 = 1 이겠군요.


R5(프레임포인터) = 100

R6(스택포인터) = 100

R7(프로그램카운터) = 1


2. main 함수 2라인 - m1 지역 변수 할당



일단 R6 가 가르키는 곳에 변수 m1를 할당합니다.

그리고 R6를 1 감소 시킵니다.


R6 = R6 -1;


R5 = 100

R6 = 99 (100 -1)

R7 = 2




3. main 함수 3라인 - m2 지역 변수 할당


또 다시 R6 가 가르키는 곳에 변수 m2를 할당합니다.

그리고 R6를 1 감소 시킵니다.


R5 = 100

R6= 98 (99 -1)

R7 = 3





4. A 함수 호출 9라인 - A함수 호출



여기가 조금 중요한 곳입니다. 일단 몇가지를 생략했다는걸 알아 주시기 바랍니다.


함수가 바뀌므로

R5를 R6가 가르키는 것으로 대입시킵니다.


R5 = R6(98) --> R5 = 98

R6 = 98

R7 = 9






5. A 함수 10라인~12 - 지역 변수 a1, a2 할당








6. A 함수 종료 1


여기서  A함수가 종료 됩니다.

R6(스택 포인터)를 R5값인 98로 바꿉니다

R6 = R5(98)


R5 = 98

R6 = R5(98) --> R6 = 98

R7 = 13




7. A 함수 종료 2



이제 main 함수 5라인으로 복귀합니다.

여기서 다시 R5 = 100으로 바꾸고

R7를 5로 바꿉니다.


R5 = 100(?)

R6 = 98

R7 = 5(?)



****** 중요

여기서 의문인점이 생깁니다.

도대체 main 함수의 스택 위치100은 어디에서 왔으며(어디에 저장되어 있나?)

다음 실행라인(pc, R7)5라인은 어디에서 왔을까요?(어디에 저장되어 있나?)

이것은 다음 게시물에서 다루겠으며 어디에 저장할지 추측해 보세요.



7. 지역 변수 m1 에 값 1을 대입하기



이제 R5(프레임 포인터)는 도대체 언제 쓰이는지 알아 볼 단계가 왔습니다.


지역 변수 m1에 1을 할당하려고 합니다.


m1 = 1;


그러면 m1 주소인 100번지에 값을 1을 넣으면 되겠지요.

하지만 위의 main() 함수니까 그나마 주소값이 고정 되어 있을지도 모르지만

A() 함수 값은 함수는 main()에 호출할수도 있고 또한 여기에 없는 또다른 함수에서 호출 할수도 있습니다. 여기서는 2번째에 호출되었지만 3, 4번째로 호출 할수 있으므로 주소가 수시로 바뀔수 있습니다.


그래서 지역변수를 접근할때  R5(프레임 포인터)가 사용됩니다.

R6(스택 포인터)는 값을 넣을때마다 수시로 변경이 되기 때문에

해당 함수가 끝나기 전까지 바뀌지 않는 R5를 사용하게 됩니다.


변수 m1는 100번지이지만

R5로 만들어 보면 R5 - 0(100-0) 값이 됩니다.

이런식으로 m2도 R5 - 1(100-1) 99가 됩니다.


그러므로 C언어로 한다면

m1 = 1; 은

(int*)(R5 - 0) = 1; --> (int*)(100) = 1;


m2 = 2; 는

(int*)(R5 - 1) = 2; --> (int*)(99) = 2;


또한 함수가 종료 될때 R6(스택 포인터)를 초기화 할때도 쓰입니다.(6. A 함수 종료 1 참고)


지역 변수는 해당 함수가 실행될때만 살아 있으므로 이는 R5를 사용하기에 알맞습니다.



그럼 다음 게시물에서  위에 빨간 글씨로 쓰인 뜻을 알아 보도록 하겠습니다.





마지막으로  지금까지의 강좌 gif 이미지



함수 호출과 이로 인해 생기는 스택 구조에 대해 간략 적으로 설명해 보겠습니다.

정말 간략적이기 때문에 상당 부분을 생략했습니다.(리턴 주소, 함수 매개변수, 환경프레임 주소 저장 등등... 생락)

다음 게시글에서 좀더 자세한 설명을 추가 할테니 일단은 개념 정도만 설명하도록 하겠습니다.


일단 C언어스택 부터 보시겠습니다.






간단합니다. main 함수 A 함수 달랑 두개가 있습니다.

그 안에 지역변수각각 2개씩 들어 있습니다.

그럼 처음 스택 구조를 보겠습니다.



스택은 데이터가 추가 되면 위에서 아래로 추가 됩니다.

옆에 있는 주소는 그냥 임의로 추가 하였고

원래는 2(16비트), 4(32비트) 단위로 감소 해야 하지만 편의를 위하여 간략하게 만들었습니다.



그럼 여기서 C언어에서 12라인까지 실행되면 스택 구조가 어떻게 되는지 대략적으로 그려보겠습니다.







대략 위와 같은 그림이 됩니다.

12라인을 실행할때 main()의 지역 변수 2개와 A()의 지역 변수 두개가 스택에 각각 위치합니다.

프로그램 수업이나 책에 보면 지역 변수는 스택에 저장 된다고 배우시잖아요.

저 그림이 그것입니다.


옆에 보시면 main()함수 구역 스택과 A()함수 스택 구조가 나뉘어있습니다.(논리적으로)

이 나뉜 부분이 unix v6책 에서 r5레지스터 환경 프레임을 가르킨다고 했는데

저 함수 구역이 환경 프레임이라고 보시면 됩니다.


그리고 하늘색으로 표시된 화살표가 다음에 스택에 데이터를 집어 넣을 장소를 가르키며

unix v6책 에서는 r6레지스터(sp - 스택 포인터 레지스터)가

저곳을 가르키는 주소(위 그림에서 주소 96)를 가지고 있습니다.

만약 스택에 데이터가 추가 되면 r6 레지스터 값이 하나가 감소(r6 = r6 - 1) 하는것이죠


그리고 주황색 화살표를 보시면 현재 함수 시작위치를 가르키고 있는데

이것이 바로 r5레지스터(인텔계열은 ebp 레지스터)가 가지고 있는 주소 값(위 그림에서 주소98)입니다.


main함수가 실행 중이면 r5 레지스터는 주소 100을 가지고 있고

A함수가 실행중이면 r5레지스터는 98를 가지고 있습니다.


다음 게시물에서는 main 함수 라인별로 따라가며 해보도록하겠습니다.


함수 호출과 스택 구조 2

http://fehead.tistory.com/200


+ Recent posts