데비안(Debian)이나 우분투(Ubuntu)가 엄청 발전해서 쓰기가 많이 좋아진 것은 사실이지만, 조금만 깊게 들어가면 여전히 손볼 대가 많습니다. ^^;;; 이번에는 커널을 직접 컴파일했을 때 흔히 발생하는 블랙 스크린, 즉 데스크탑 화면이 표시되지 않고 검은색 바탕에 화면 좌측 상단에 커서만 깜빡이는 현상 처리 방법입니다.

이런 상황은 보통 Nvidia나 ATI에서 제공하는 3rd party 드라이버를 사용하는 경우에 발생합니다. 저는 오래된 Nvidia 그래픽카드를 쓰고 있는데요, 아래처럼 패키지를 다시 설치하는 방법으로 해결하고 있습니다. 새로운 커널에서 로딩할 Nvidia 드라이버가 없어서 발생하는 문제거든요. 

# Nvidia의 커널 모듈을 다시 컴파일해주는 패키지입니다.
$> sudo apt reinstall nvidia-kernel-dkms

커널 컴파일 후 비슷한 문제가 생길 때마다 스택 오버플로우(Stack Overflow) 사이트에서 찾아 쓰다가, 생각난 김에 정리해둡니다.

그럼 즐거운 밤 되세요 >ㅁ<)/~

언제부터인지 데비안(Debian)이나 우분투(Ubuntu) 커널을 빌드하고 모듈을 인스톨하면 디버그 심볼이나 기타 정보가 엄청 생성돼서 파일이 너무 커지는 일이 발생했습니다. 사실 모듈이 커지는 것쯤은 별일이 아닐 수 있지만, 문제는 램디스크, 즉 initrd를 생성할 때 발생합니다. 모듈이 너무 커서 initrd 사이즈가 500MB까지 증가하거든요. ^^;;;

저는 커널 보안기능을 주로 개발하기 때문에 수시로 커널을 빌드하는데요, 그러다 보면 커널 모듈 크기 때문에 initrd 빌드 시간이 길어져서 빌드 완료까지 대기하는 전체 시간(이라고 쓰고 노는 시간이라고 읽는... 쿨럭..;;)도 길어지더라구요. 그래서 방법을 찾다 보니 아래처럼 하면 된다는 걸 발견했습니다.

# 먼저 커널과 커널 모듈을 빌드합니다.
$> make -j <cpu 갯수>
$> make modules -j <cpu 갯수>

# 불필요한 정보를 제거한 후 모듈을 설치하고 커널을 설치합니다.
$> make INSTALL_MOD_STRIP=1 modules_install -j <cpu 갯수>
$> make install

이제 시간을 효율적(?!)으로 쓸 수 있겠네요!

그럼 즐거운 저녁 되세요 ㅠㅠ)/

최근 다시 여유가 생겨서 그동안에 쌓아두었던 책을 처리(?!)하고 있습니다. ^^;;; 첫 번째 책은 "프리-오픈소스 소프트웨어 혁명의 역사"인데요, 가벼운 마음으로 읽기 시작했는데... 생각보다 흥미진진하고 생각할 거리가 많은 책이었습니다. 비슷한 책으로는 한빛미디어에서 나온 "유닉스의 탄생: 세상을 바꾼 운영체제를 만든 천재들의 숨은 이야기"가 있는 것 같습니다. "유닉스의 탄생"이 좀 더 말랑말랑한... 그러니까 할아버지께 옛날이야기를 듣는 듯한 느낌이라면, 이 책은 뭐랄까요... 사실에 입각한 논문을 읽는... 아주 건조한 느낌이 듭니다. 쿨럭..;;;

프리-오픈소스 소프트웨어 혁명의 역사-지식함지

그도 그럴 것이, 이 책은 그동안 다양한 방법으로 정리를 시도했던 유닉스, 프리 소프트웨어, 오픈소스 소프트웨어 간의 관계를 한 데 묶을 목적으로 나왔기 때문에... 수많은 참고자료를 바탕으로 여러 사건들을 시간에 흐름에 따라 정리하고 있거든요. 그래서 첨가물, 전문용어로 MSG를 최대한 뺀 것이죠. ^^;;;

재미는 좀 덜하지만, 벨(Bell) 연구소의 유닉스(Unix), 리처드 스톨먼(Richard Matthew Stallman)을 비롯한 해커들의 자유 소프트웨어(free software) 및 GNU 활동, 라이너스 토발즈(Linus Benedict Torvalds)의 리눅스(Linux), 이안 머독(Ian Murdock)의 데비안(Debian)의 관계를 아주 명료하게 풀어내 줘서 저한테는 큰 도움이 되었습니다. 제가 또 이런 인과나 연관 관계에 흥미가 많거든요. ^^)/

알고 있던 낱개의 사건이 하나로 이어져서 서로 영향을 주고받았다는 것을 알고 나니... 이 책의 진가가 눈에 보였습니다. 일례로 리눅스에 관심이 많은 분이라면 데비안(Debian)을 한 번쯤은 들어보셨을 텐데요, 스톨먼이 데비안을 지지했다는 사실은 이 책을 보고 처음 알았습니다. ^^;;;

혹시 자유 소프트웨어, 오픈소스, 리눅스 등에 관심이 있다면, 꼭 한 번 읽어보시길 바랍니다.

그럼 즐거운 저녁 되세요 >ㅁ<)/

요즘 진행 중인 프로젝트가 있는데, 어찌하다 보니 디버깅을 위해서 버추어박스의 소스코드를 빌드할 일이 생겼습니다. 전체적인 빌드 과정은 https://www.virtualbox.org/wiki/Linux%20build%20instructions에 잘 정리되어 있긴 한데요... 아시겠지만 실제로 빌드를 해보면 다양한 문제에 봉착하게 되죠... 쿨럭..;;; 오라클 버추어박스의 소스코드를 빌드를 하다 보면 아래와 같은 오류가 발생합니다.

/home/user/project/VirtualBox-6.1.16/doc/manual/en_US/user_KnownIssues.xml:6: warning: failed to load external entity "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd"
]>
  ^
/home/userproject/VirtualBox-6.1.16/doc/manual/en_US/user_ChangeLog.xml:6: warning: failed to load external entity "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd"
]>
  ^
/home/user/project/VirtualBox-6.1.16/doc/manual/en_US/user_ThirdParty.xml:6: warning: failed to load external entity "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd"
]>
  ^
/home/user/project/VirtualBox-6.1.16/doc/manual/en_US/user_PrivacyPolicy.xml:6: warning: failed to load external entity "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd"
]>
  ^
/home/user/project/VirtualBox-6.1.16/doc/manual/en_US/user_Glossary.xml:6: warning: failed to load external entity "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd"
]>
  ^
warning: failed to load external entity "/usr/share/xml/docbook/schema/dtd/4.5/docbookx.dtd"
Could not parse DTD /usr/share/xml/docbook/schema/dtd/4.5/docbookx.dtd
kBuild: Validating /home/user/project/VirtualBox-6.1.16/doc/manual/en_US/UserManual.xml

실제로 docbookx.dtd 파일에 문제가 있는 건 아닌 것 같고, 소스코드 tarball에서 파일이 한 개 없어서 발생한 오류였습니다. 아래와 같이 입력해서 빈 파일을 만들어주면 실제로 빌드가 잘 됩니다.

user@user:~/project/VirtualBox-6.1.16$ touch out/linux.amd64/release/obj/manual/en_US/validatemanual.run
user@user:~/project/VirtualBox-6.1.16$ chmod +x out/linux.amd64/release/obj/manual/en_US/validatemanual.run

뭔가 일부러 빠뜨렸을 것 같지는 않은데... 그리고 벌써 몇년이나 지난 버그인데, 아직도 고쳐지지 않았더라고요. 여하튼 빌드가 잘 되어 천만다행입니다. ^^;;;

그럼 좋은 밤 되세요.

pip로 재미있는(?) 툴을 설치할 일이 있었습니다. 그런데 이게 웬일? 데비안(Debian)에는 pip가 기본적으로 설치되어 있지 않더라고요. 사실 pip는 파이썬으로 만든 툴을 설치하는 용도로 많이 사용했던지라 좀 당황했습니다. 허나, 찾으려고 하면 또 찾아지는 법이더라고요. ^^;; 데비안 계열에서 설치하려면 아래와 같이 입력하면 됩니다.

$> sudo apt install python-pip python-setuptools python3-pip python3-setuptools

$> sudo apt install python-pip python-setuptools python3-pip python3-setuptools

그럼 즐거운 리눅스 생활되세요 >ㅁ<)/

i3 윈도우 매니저는 불친절하기 그지없는데요, 배경 이미지를 설정하는 것조차 커맨드로 처리해야 합니다. ㅠㅠ feh라는 도구를 활용해서 말이죠. 아래는 ~/.config/i3/config 파일을 수정해서 매번 로그인할 때마다 배경을 로딩하도록 해주는 코드입니다.

# 절대 경로와 함께 아래 라인을 추가해줘야 함
exec --no-startup-id feh --bg-scale /home/user/사진/background.jpg

아아... 정말 불친절하기 그지없네요. 뭔가 한 방에 다 깔끔하게 처리해주는 패키지가 있으면 좋을텐데 말이죠. ^^;;;
그럼 좋은 하루 되세요 >ㅁ<)/

2021년에도 Black Hat Aisa 리뷰 보드로 활동을 하게 되어 보게 된 논문인데, 보안 관련 3대 학회로 손꼽히는 ACM CCS 2020에 채택된 논문입니다. 제목에서도 알 수 있듯이 리눅스 커널 공격에 사용되는 elastic object, 즉 우리말로 옮기면 크기가 가변적인 객체와 관련해서 체계화 연구를 수행한 내용입니다.

Elastic object의 핵심은 구조체 내부에 버퍼나 버퍼의 주소를 가리키는 포인터가 있고, 그리고 길이를 나타내는 필드가 있다는 점입니다. 이러한 특징으로 인해 elastic object의 버퍼 포인터나 길이를 조절하면 임의의 커널 메모리 읽기(arbitrary read)나 범위를 넘겨 읽기(out-of-bound read, OOB read)가 가능하고, KASLR(Kernel Address Space Layout Randomization)을 무력화할 수 있는 커널 함수 포인터나 Stack canary 등을 읽어낼 수 있죠.

Elastic object 공격으로 인한 영향 - 출처: A Systematic Study of Elastic Objects in Kernel Exploitation

사실 이러한 객체는 흔한 듯하면서도 흔치 않은데요, 커널 구조체의 대부분은 크기가 고정되어 있거든요. ^^;;; 이 논문에서는 아래처럼 elastic object를 찾아서 리스트를 만들어 놓고, 이와 관련해서 커널 데이터를 유출할 수 있는 함수들도 정리해두었습니다. 이 과정에서 정적/동적 분석 기법을 활용했다고 하는데, 오탐을 줄이기 위해 함수 리스트 같은 경우는 수동으로(manual) 선정했다고 하는군요.

Elastic kernel objects - 출처: A Systematic Study of Elastic Objects in Kernel Exploitation 
커널에서 유저로 데이터를 전달할 수 있는 함수 목록 - 출처:A Systematic Study of Elastic Objects in Kernel Exploitation

개인적으로 이 논문의 의의는 elastic object 관련 내용을 체계화했다는 것에 있다고 생각합니다. 임의 메모리 읽기나 범위 넘겨 읽기 같은 경우는 논문의 위협 모델(thread model)에도 나와 있지만, 공격자가 이미 임의 메모리 쓰기 취약점(arbitrary memory write)을 가지고 있다고 가정하기 때문에 영향력이 크지 않다고 생각합니다. 사실 임의 메모리 쓰기 취약점이 임의 메모리 읽기 취약점보다 귀하기 때문에 이런 취약점이 더 영향력이 크고 더 유용하거든요. 물론, 제대로 쓰기 위해서는 논문에서 기술한 것처럼, 임의 메모리 읽기 취약점을 이용해서 KASLR을 무력화해야 좀 더 공격 성공 확률이 올라가긴 하지만요. ^^;;;

해결책(mitigation)의 경우도 격리(isoloation) 기법을 사용했는데요, 이 방법 또한 일반적으로 활용하는 방법입니다. 격리를 하면 elastic object만 따로 떨어지는데, 인접해있는 kernel object를 통해 주요 정보를 유출할 수 있는 기회가 적어지긴 합니다. 다만, 메모리 풀(pool)을 분리해야 하므로 코드 수정이 필요하고, 이로 인한 부수효과(side effect)가 어떠한 형태로 나타나는지 심도 깊은 평가도 필요하지요. 논문에서도 성능 등의 여러 관점에서 평가를 하는데요, 실험실(lab) 환경과 실제 운영 환경은 다르기 때문에 논문의 결과가 실제 필드로 적용되기까지는 많은 시간이 필요합니다. ^^;; 참고로 최근에 도입된 꽤 유명하고 대표적인 격리 기법에는 KPTI(Kernel Page Table Isolation)가 있습니다. 바로, 세간을 떠들썩하게 한 멜트다운(meltdown) 취약점을 막기 위해 도입한 것이죠.

리뷰보드 활동 때문에 새해 첫날부터 머리가 아프... 여튼 흥미로운 논문이었습니다.

그럼 새해 복 많이 받으세요!!

i3 윈도우 매니저(Window Manager)를 사용하다 보니 이것저것 아쉬운 부분이 있는데요, 이번에는 볼륨 컨트롤입니다. XFCE나 GNOME, KDE 같은 윈도우 매니저들은 웬만한 기능들을 다 가지고 있지만 i3 윈도우 매니저는 대부분 다른 것들을 빌려서 사용해야 합니다. ^^;;; 쿨럭...;;;

볼륨 컨트롤 같은 경우는 GTK로 된 Pulseaudio Volume Control을 설치하니 사용하는데 큰 불편함이 없더라구요. 데비안 계열에서는 아래와 같이 설치하고 사용할 수 있습니다.

# 설치
$> sudo apt install pavucontrol

# 실행
$> pavucontrol

그럼 즐거운 하루 되세요. ^^)/

요즘 제가 메인으로 사용하고 있는 i3 윈도우 매니저(Window Manager, wm)는 틸팅(Tilting)이라는 멋진 기능이 있어서 많은 유저를 확보하고 있는데요, 반면에 GUI로 제공되는 그놈 컨트롤 센터(Gnome Control Center) 같은 툴이 없어서 불편한 점도 있습니다. 그래서 모니터 설정이나 밝기 같은 부분은 xrandr 같은 툴을 활용해야 하죠. ^^;;;

먼저 모니터의 위치를 변경하는 방법입니다. 아래처럼 xrandr --listmonitors를 하면 현재 연결된 모니터를 확인할 수 있는데요, i3 윈도우 매니저는 연결된 모니터를 순서대로 오른쪽에 배치를 해줍니다. 노트북 같은 경우는 내장 모니터와 외장 모니터로 구분되고, 내장 모니터가 왼쪽에, 외장 모니터가 오른쪽에 자동으로 배치됩니다.

$> xrandr --listmonitors
 0: +*eDP-1 1920/344x1080/194+2560+0  eDP-1     <== 내장 모니터
 1: +DP-1 2560/698x1440/392+0+0  DP-1                  <== 외장 모니터

이러한 순서를 바꾸려면 아래와 같이 입력하면 됩니다. --left-of 나 --right-of를 이용해서 말이죠. :)

# 내장 모니터의 왼쪽에 외장 모니터를 위치시킴
$> xrandr --output DP-1 --auto --left-of eDP-1

xrandr는 소프트웨어적으로 밝기도 조절할 수 있는데요, 아래처럼 --brightness를 이용하면 됩니다.

$> xrandr --output eDP-1 --brightness 0.7

그럼 즐거운 하루 되세요. ^^)/

가벼운 윈도우 매니저를 찾다가 올해부터 본격적으로 i3wm을 사용하기 시작했습니다. ^^;;; i3wm의 가장 큰 매력은 틸팅(Tilting) 기능인데요, 창이 뜰 때마다 화면의 절반을 갈라서 자동으로 분할 및 배치를 해준다는 점입니다. 저처럼 개발을 주로 하고 화면을 양분할 해서 코딩 윈도우와 검색 윈도우를 배치하는 사람한테는 멋진 기능이 아닐 수 없죠. 특히 키보드로 창 이동과 배치 등등이 가능한 부분은 마치 VIM을 윈도우 매니저로 사용하는 것 같은 착각을 불러일으키기도 하죠. ^^a...

i3wm의 윈도우 배치 화면

이런 편리한 기능도 가끔은 당황스러울 때가 있는데요, 바로 파일 다이얼로그처럼 기존의 창 위에 표시되는 다이얼로그도 강제로 틸팅되어 배치되기 때문입니다. 그래서 매번 파일 다이얼로그가 뜰 때마다 어색한 정적이 흐르는데요... i3wm의 설정파일을 아래와 같이 수정하면 파일 다이얼로그가 뜰 때 다른 창 위에 자연스럽게 표시됩니다.

# 설정 파일은 ~/.config/i3/config에 있습니다.

bindsym $mod+n exec nautilus --class floatingWM
for_window [class="^floatingWM$"] floating enable

# 위의 파일을 저장하고 아래와 같이 입력하면 바로 적용됩니다.
$> i3 reload
$> i3 restart

다른 윈도우 매니저도 좋지만 VIM처럼 쓸 수 있는 i3wm이 제일 손에 맞는 것 같네요.

그럼 즐거운 하루 보내세요 ^^)/

가끔 임시 비밀번호를 만들어 써야하는 경우가 종종 있는데요, 임시 비밀번호지만 뭔가 습관대로 만드는 것이 불안해서 임의의 문자열을 뽑는 기능을 검색해봤습니다. 다양한 방법이 있었는데 스택 오버플로우(unix.stackexchange.com/questions/230673/how-to-generate-a-random-string)에 좋은 방법이 있었습니다. ^^)/~ 즉, 아래처럼 하면 된다는 이야기지요.

$> head /dev/urandom | tr -dc A-Za-z0-9 | head -c 32 ; echo ''
uqpmpi0CpJOEh1NJL9tffi6mmPLe6ASk

$> head /dev/urandom | tr -dc A-Za-z0-9 | head -c 32 ; echo ''
cnxc8yYGGrZwz5noljO18yg8xNBH9kHY

$> head /dev/urandom | tr -dc A-Za-z0-9 | head -c 32 ; echo ''
YUvAlrdtyNUhrr5GSXLKLeA7EkSoZuTV

꽤나 유용하게 사용할 수 있겠군요.

그럼 좋은 하루 되세요 ^^)/

리눅스를 사용하다보면 배포판(Distribution)이 지원해주지 않는 디바이스를 써야할 때가 있습니다. 사실 그래서 저는 한 세대 전 장비를 선호하기도 한데요, 어쩔 수 없이 써야할 경우가 종종 있습니다. 예를 들면 최신 장비에서 제가 만든 소프트웨어가 잘 동작하는지를 봐야 하는 경우처럼요. ^^;;;

보통 이런 경우는 강제로 커널을 최신 버전으로 업그레이드 하면 되지만 이것 또한 쉽지 않은 경우가 있습니다. 배포판의 경우는 특정 커널 버전을 정해놓고 유지하면서 최신 버전 커널은 천천히 적용하는 게 대부분이거든요. 그렇다면 옛날 커널 버전에서 최신 디바이스의 드라이버를 지원하려면 어떻게 해야 하느냐... 리눅스 커널의 백포트(backport)를 쓰면 되는데요, 백포트는 git.kernel.org/pub/scm/linux/kernel/git/iwlwifi/backport-iwlwifi.git에 있습니다. 다만, 이걸 직접 받아서 빌드하는 것이 쉽지 않은데, 데비안(Debian) 리눅스의 파생 버전인 우분투(Ubuntu) 리눅스는 이를 위한 패키지를 이미 준비해두었더라구요.

Canonical Hardware Enablement team이 이를 담당하고 있는데요, launchpad.net/~canonical-hwe-team/+archive/ubuntu/pc-oem-dkms에서 backport-iwlwifi-dkms_7906-0ubuntu3~18.04.1~oem~ubuntu18.04.1_all.deb를 받으면 됩니다. 그리고 아래처럼 설치하면 설치 중에 자동으로 커널 드라이버가 빌드됩니다.

$> sudo dpkg -i backport-iwlwifi-dkms_7906-0ubuntu3~18.04.1~oem~ubuntu18.04.1_all.deb

제가 확인해본 결과 데비안 10(Buster)에서도 일단 정상적으로 동작하는 것을 확인했고, 최신 WIFI 디바이스인 Killer AX1650도 잘 잡혔습니다. ㅠㅠ)-b 후우... 정말 다행이에요.... 지옥문이 열릴 뻔 했거든요. 쿨럭..;;;

그럼 해피 리눅스하세요. >ㅁ<)/

최근 데비안(Debian) 리눅스에서 그놈(Gnome) 데스크탑을 버리고 i3 윈도우 매니저를 사용하기 시작했습니다. i3는 틸팅(Tilting), 화면을 이등분 해서 창을 정렬하는 것이 특징인데요, 처음에는 좀 당황스럽지만 익숙해지면 vi를 사용하듯이 창을 옮겨다니면서 쓸 수 있습니다. 물론 이렇게까지 익숙해지려면 시간을 많이 투자해야 한다는 것이 함정이지만요... 쿨럭..;;;;

그놈을 버리고 나니 아쉬운 부분이 여럿 있는데요, 그중에 하나가 모니터 밝기 조절입니다. gnome-control-center를 실행해서 해결해 볼까도 했지만 커맨드 라인으로 처리할 수 있는 방법을 찾았습니다. 방법은 간단한데요, xrandr로 연결된 모니터를 찾고 밝기를 조절하는 겁니다. 아래처럼 말이죠. ^^;;;

# 연결된 모니터를 찾음.
$>xrandr -q | grep connected
eDP-1 connected primary 1920x1200+0+0 (normal left inverted right x axis y axis) 263mm x 164mm
DP-1 connected 2560x1440+1920+0 (normal left inverted right x axis y axis) 698mm x 392mm
HDMI-1 disconnected (normal left inverted right x axis y axis)

# 모니터의 밝기를 70%로 조절함.
xrandr --output eDP-1 --brightness 0.7

생각보다 간단하네요. 그럼 좋은 하루 되세요 ^^

리눅스(linux) 환경에서 권한 상승이 필요할 때 많이 쓰는 방법이 sudo를 이용하는 것인데요, 다들 아시겠지만 sudo를 사용하려면 sudo 패키지가 시스템에 설치되어 있어야 합니다. 그리고 _etc_sudoers 파일에 sudo를 사용할 계정이 추가되거나 sudo 그룹에 가입되어야 하지요.

일반적인 상황은 아니지만, 간혹 자동화를 목적으로 사용자의 패스워드 입력 없이 특정 실행파일을 관리자(root) 권한으로 실행해야 할 경우가 있습니다. 이런 경우, 사용할 수 있는 방법이 _etc_sudoers 파일에 패스워드 입력을 생략하는 내용을 추가하는 건데요, 사용자 명이 user이고 관리자 권한으로 실행해야할 파일이 _usr_bin/xeyes라면 다음과 같이 마지막줄에 추가하면 됩니다.

# /etc/sudoers 파일의 내용
... 생략 ...

#includedir /etc/sudoers.d
user    ALL=(ALL)   NOPASSWD:   /usr/bin/xeyes

만약 user의 경우는 모든 파일을 패스워드 입력없이 사용하고 싶다면 아래와 같이 변경하시면 됩니다.

user ALL=(ALL)  NOPASSWD:   ALL

그럼 좋은 하루 되세요 ^^

요즘 만자로(Manjaro) 리눅스를 주로 쓰고 있는데요, 사용하는 PC의 NVIDIA 그래픽카드와 궁합이 좋지 않은지 화면이 찢어지는 문제가 발생했습니다. ㅠㅠ 눈 상태가 요즘 점점 나빠지고 있는데 화면까지 그런 문제가 생기니 어떻게든 해결해야겠다는 생각이 들더라구요. 아. 물론... 쉬는 게 가장 좋기는 한데... 요즘 발표 요청이 많이 들어와서 주말에도 바쁜 나날을 보내고 있습니다. ㅠㅠ

그렇게 찾다보니, 드라이버를 다시 한번 설치 봐야겠다고 알아보니, 최신 NVIDIA 드라이버를 다운받아서 설치하는 방법이 있었습니다. 이렇게 해보니 찢어지는 현상이 없어졌네요. ^^;;; 결국 드라이버의 문제였던 듯... ㅠㅠ

만자로에서 NVIDIA 드라이버를 설치하는 방법은 아래와 같습니다.

# 아래처럼 입력해서 만자로 하드웨어 디텍터로 설치하고 리부팅합니다.
$> sudo mhwd -a pci nonfree 0300
$> sudo reboot

# 리부팅 후 아래처럼 입력하면 NVIDIA 설정 프로그램을 실행할 수 있습니다.
$> nvidia-settings

최신 NVIDIA 드라이버를 만자로 리눅스에 설치하는 방법은 공식 사이트를 참고하세요.

만자로 공식 사이트: https://linuxconfig.org/how-to-install-the-nvidia-drivers-on-manjaro-18-linux

그럼 좋은 하루 되세요 ^^

만자로 리눅스(Manjaro Linux)는 아치(Arch) 리눅스 기반에 롤링 릴리즈를 장점으로 내세운 배포판인데요, 요즘 주 배포판으로 사용하고 있습니다. 기본 설치 후 한글 관련 설정만 해주면 쓸 수 있을 정도로 꽤나 잘 관리되어 있는데요, 한글 관련 설정 방법은 아래와 같습니다.

# iBus 한글 설치
$> sudo pacman -S ibus-hangul ibus-qt

# 설치 후 홈 디렉토리에 $HOME/.xprofile을 생성하고 아래 내용을 추가
$> vi $HOME/.xprofile
export GTK_IM_MODULE=ibus
export XMODIFIERS=@im=ibus
export QT_IM_MODULE=ibus
export OOO_FORCE_DESKTOP="gnome"
ibus-daemon -drx

그리고 로그아웃 및 로그인을 하면 한글 입력기가 실행됩니다.

그럼 좋은 하루 되세요 ^^)/~

요즘 만자로 리눅스(Manjaro Linux)를 주로 사용하고 있는데요, 만자로를 쓰다보면 제일 먼저 당황하는 것이 한글 입력 설정과 마우스 클릭 시 바로 실행되는 옵션입니다. 보통은 더블클릭을 해야 실행에 옮기기 때문에 이러한 기본 설정은 상당히 당황스러운데요, $HOME/.config/kdeglobals 설정 파일을 변경함으로써 처리할 수 있습니다.

$> vim ~/.config/kdeglobals

[KDE]
# 아래 라인 추가
SingleClick=false

그리고 로그아웃 후에 다시 로그인하면 마우스 클릭이 아닌 더블클릭으로 실행할 수 있습니다.

그럼 좋은 하루 되세요 >ㅁ<)-b

응용프로그램을 디버깅하다보면 항상 고정된 위치에 응용프로그램이 로딩되어야 할 필요가 있는데요, 최신의 리눅스에는 해킹 방지를 위해 ASLR(Address Space Layout Randomization) 기능이 활성화되어 있어서 실행할 때마다 항상 다른 위치에 응용프로그램이 로딩됩니다. ㅠㅠ 좋은 기능이긴 한데... 디버깅시에는 크게 도움이 안되지요. ㅠㅠ

이 기능을 비활성화하려면 다음과 같이 파일을 추가하면 됩니다.

$> sudo vi /etc/sysctl.d/01-disable-aslr.conf

kernel.randomize_va_space = 0

아우 디버깅이 빨리 끝나야할텐데... 걱정이네요. ㅠㅠ

그럼 좋은 밤 되세요. ^^

최근에 팀을 옮기면서 리눅스 관련 일을 하게 되었습니다. ^^;;; 리눅스는 사실 MINT64 OS를 만들 때 리눅스 커널 구조를 본 게 전부라... 제가 잘 할 수 있을 까 걱정이 많이 되었는데요, 아직까지는 별 탈 없이 잘 지내고 있습니다. ^^;;;

최근에 옮긴 팀에서는 주로 리눅스 커널 관련 일을 하고 있는데요, 그러다보니 간단한 커널 모듈을 만들 일이 많아 코드를 남겨둡니다. 아래 코드는 Loadable Kernel Module 형태로 insmod를 통해 로딩되면 /dev/hello 디바이스를 생성하고 통신하는 역할을 합니다. ^^

1. 커널 모듈 소스 코드 작성 및 Makefile 작성

터미널에서 vi hello.c를 입력한 뒤 아래 코드를 붙여넣고 저장합니다.

/**
 *  본 파일은 프레임워크 예제 파일임
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/syscalls.h>
#include <linux/fcntl.h>
#include <linux/cred.h>

int in_use = 0;

// 모듈 정보
MODULE_LICENSE("HIDDEN");
MODULE_AUTHOR("root");
MODULE_VERSION("1.0");
MODULE_DESCRIPTION("Hello World");

/**
 *  /dev 밑에 등록한 파일을 누가 열었을 때 호출되는 함수
 */
static int my_open(struct inode *inode, struct file *file)
{
    // 해당 파일을 동시에 여러개를 열지 못하도록 하는 코드
    if(in_use)
    {
        return -EBUSY;
    }

    in_use++;
    printk(KERN_INFO "Hello Open\n");

    return 0;
}

/**
 *  /dev 밑에 등록한 파일을 누가 닫았을 때 호출되는 함수
 */    
static int my_release(struct inode *inode, struct file *file)
{
    in_use--;
    printk(KERN_INFO "Hello Closed\n");
    return 0;
}

/**
 *  /dev 밑에 등록한 파일을 누가 닫았을 때 호출되는 함수
 */
static int my_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    int retval = 0;
    printk(KERN_INFO "Hello Ioctl~!!\n");
    return retval;
}

/**
 *  /dev 밑에 등록한 파일을 누가 읽었을 때 호출되는 함수
 */
static ssize_t my_read(struct file *filp, char *buffer, size_t length, loff_t *offset)
{
    printk (KERN_INFO "Hello Read~!!.\n");
    //printk (KERN_INFO "%X\n", sys_call_table);
    return 0;

    // 실제로 Read 명령을 처리하는 코드, 일단 예제로 남겨둠
    /*
    // Number of bytes actually written to the buffer
    int bytes_read = 0;

    // If we're at the end of the message, return 0 signifying end of file
    if (*msg_Ptr == 0) return 0;

    // Actually put the data into the buffer
    while (length && *msg_Ptr)  
    {
        put_user(*(msg_Ptr++), buffer++);

        length--;
        bytes_read++;
    }

    // Most read functions return the number of bytes put into the buffer
    return bytes_read;
    */
}

/**
 *  /dev 밑에 등록한 파일에 누가 썼었을 때 호출되는 함수
 */
static ssize_t my_write(struct file *filp, const char *buff, size_t len, loff_t *off)
{
    printk (KERN_INFO "Hello Write~!!.\n");
    return -EINVAL;
}

/**
 *  /dev/hello 파일 등록시 필요한 file operation 구조체
 */
static const struct file_operations my_fops = \
{
    .owner = THIS_MODULE,
    .open = &my_open,
    .read = &my_read,
    .write = &my_write,
    .release = &my_release,
    .unlocked_ioctl = (void*) &my_ioctl,
    .compat_ioctl = (void*) &my_ioctl
};

/**
 *  /dev/hello 파일 등록시 필요한 device 구조체
 */
static struct miscdevice my_device = \
{
    MISC_DYNAMIC_MINOR,
    "hello",
    &my_fops
};

/**
 *  커널 드라이버 시작 함수
 *      - 커널 드라이버가 로딩될 때 호출됨
 */
static int __init hello_init(void)
{
    printk(KERN_INFO "Hello, world\n");    

    int retval = misc_register(&my_device);
    return 0;
}

/**
 *  커널 드라이버 종료 함수
 *      - 커널 드라이버가 언로딩될 때 호출됨
 */
static void __exit hello_exit(void)
{   
    printk(KERN_INFO "Goodbye, world\n");
    misc_deregister(&my_device);
}

// 시작 함수와 종료 함수 지정 매크로
module_init(hello_init);
module_exit(hello_exit);    

코드를 다 붙여넣었으면 이제 터미널에서 vi Makefile을 입력한 뒤 아래 코드를 붙여넣고 저장합니다.

obj-m += hello.o

all:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Makefile을 생성한 후에 make를 입력하면 자동으로 빌드가 시작되고 다음과 같이 hello.ko가 생성됩니다.

[root@BuildMachine module]# ll
합계 244
-rw-r--r-- 1 root root    154  2월 24 09:39 Makefile
-rw-r--r-- 1 root root      0  2월 24 09:39 Module.symvers
-rw-r--r-- 1 root root    351  2월 24 09:42 hello.c
-rw-r--r-- 1 root root 112027  2월 24 09:41 hello.ko
-rw-r--r-- 1 root root    454  2월 24 09:39 hello.mod.c
-rw-r--r-- 1 root root  63104  2월 24 09:39 hello.mod.o
-rw-r--r-- 1 root root  50384  2월 24 09:41 hello.o
-rw-r--r-- 1 root root     29  2월 24 09:41 modules.order

3. 커널 모듈 시험 및 결과 확인

커널 모듈은 insmod를 이용하여 커널에 동적으로 올릴 수 있구요, rmmod를 이용하여 커널에서 내릴 수 있습니다. 그리고 로딩된 모듈의 목록은 lsmod를 이용하여 확인할 수 있습니다.

커널 모듈은 슈퍼유저(root) 권한으로만 로딩할 수 있으므로, 커널 모듈 시험 전에 root 계정으로 로그인하거나 sudo와 함께 insmod를 실행합니다. 다음은 커널 모듈을 로딩하고 삭제한 뒤 그 결과를 dmesg를 통해 확인한 것입니다. printk() 출력은 dmesg나 cat /proc/kmsg 를 통해 확인할 수 있습니다.

$>insmod hello.ko
$>ls /dev/hello -l
crw------- 1 root root 10, 57  2월 26 13:59 /dev/hello

$>chmod 666 /dev/hello           <== /dev/hello를 유저 권한으로 읽고 쓸 수 있게 만듬

4. 커널 모듈 테스트 프로그램 구현 및 빌드

커널 모듈이 정상적으로 로딩되었으니 이제 테스트 프로그램을 만들 차례입니다. ^^ 프레임워크 커널 모듈은 /dev/hello에 접근하면 메시지를 출력하게 되어있습니다. 커널 모듈 테스트 프로그램의 소스 코드는 다음과 같고 터미널에서 vi test.c를 입력한 뒤 저장합니다.

/**
 * 본 파일은 프레임워크 테스트 파일임
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>

int main(int argc, char** argv)
{
    int fd;

    fd = open("/dev/hello", O_RDONLY);
    if (fd == -1)
    {   
        printf("Kernel Module Open Error\n");
    }   
    else
    {   
        printf("Kernel Module Open Success\n");
    }   
    close(fd);

    return 0;
}        

저장한 테스트 프로그램의 소스코드 코드는 다음과 같이 빌드할 수 있습니다.

$>gcc -o hello hello.c

5. 커널 모듈 기능 테스트

./hello를 실행하면 테스트 프로그램이 커널 모듈이 생성한 파일을 열어서 메시지를 표시합니다. 테스트 프로그램 실행 후 dmesg나 cat /dev/kmsg를 입력하여 커널 메시지를 확인하면 정상적으로 커널 모듈이 실행되었음을 알 수 있습니다. ^^

그럼 좋은 하루 되세요 ^^

요즘 개인적인 이유로 우분투 계열의 리눅스를 의도적으로 쓰려고 하고 있습니다. 예전에도 쓰긴 했지만 빌드 머신으로만 활용한지라... ㅠㅠ 사실 쓴다고 이야기하기도 부끄러울 정도였거든요. 그래서 열심히 써보려고 하긴 하는데, 역시나... 맨날 쓰는 것만 쓰니 마찬가지네요. ^^;;;

쓰다가보니 부팅 때마다 매번 실행하고 싶은 프로그램이 생겼는데요, 매번 실행하려니 이것도 일이더라구요. 그래서 찾아봤더니 /etc/rc.local에 실행하고 싶은 프로그램을 지정해두면 자동으로 실행할 수 있더군요. ^^ 아래처럼 말이죠.

# 매 부팅 시 /usr/test 프로그램을 자동 실행하 싶다면 /etc/rc.local에 다음과 같이 입력

/usr/test &

그럼 좋은 하루 되세요 ^^

ps) /etc/inittab에 추가하는 방법도 있습니다. respawn 옵션은 프로그램이 죽었을 때 자동으로 재실행해주는 옵션이에요 ^^)/~

MyProgram::respawn:프로그램 경로

요즘 나오는 리눅스들은 기본적으로 ASLR(Address Space Randomization) 옵션이 켜져있는데요, 이 옵션이 켜지면 프로세스가 실행될 때마다 로딩되는 주소가 바뀌게 됩니다. 크래커들의 공격을 막기위해 들어간 기능인데요, 만든 프로그램을 디버깅할 때는 오히려 불편하더라구요. 그래서 ASLR을 끄는 방법을 찾아봤더니 의외로 간단했습니다.

echo 0 > /proc/sys/kernel/randomize_va_space

콘솔에서 위의 명령을 입력하면 즉시 ASLR 옵션이 비활성화됩니다. 활성화하려면 0을 1로 바꿔주면 되겠지요. ^^)/~

그럼 좋은 하루 되세요 ^^

리눅스에서 gcc를 사용해서 코드를 컴파일하고 있는데, 뜬금없는 에러가 나왔습니다. 경고(Warning)를 에러(Error)로 취급해서 빌드를 멈춘다는 메시지였는데요, 아래와 같습니다.

cc1plus: all warnings being treated as errors

make[1]: *** 끝나지 않은 작업을 기다리고 있습니다....

사실 정확한 해결책은 경고(Warning)가 발생하는 코드를 수정하는 것이지만... 경고가 발행하는 곳이 한 두 군데가 아니라서 경고를 에러로 처리하는 옵션을 끄기로 했습니다. 사실 끄는 방법은 아주 간단한데요, gcc의 컴파일 옵션에 -Wno-error를 추가해주면 됩니다. 아래처럼 말이죠 ^^)/~

root> gcc -o a.elf -Wno-error a.c

Makefile을 이용해서 빌드한다면 CFLAG 옵션에다 추가하면 더 간단히 해결할 수 있습니다.

그럼 좋은 하루 되세요 ^^

리눅스 커널 부팅과정(Linux Kernel Booting Sequence) 분석

윈도우에 이어 리눅스도 부팅과정을 분석하고 있는데요, 소스코드가 있으니 윈도우보다는 그나마 조금 낫네요. ^^;;;; 여기저기서 자료를 좀 모았는데, 나름 정리해봅니다.

1.GRUB

윈도우에는 BOOTMGR이 있듯이 리눅스에는 GRUB이 있습니다. 이 GRUB의 역할은 Disk의 MBR 영역에 있다가 PC 부팅할 때 리눅스 커널(Linux Kernel)을 메모리에 올려주는 것입니다. GRUB은 Stage 1과 Stage 2로 구성되어 있는데요, Stage 1은 우리가 이야기하는 MBR 영역에 있는 1 Sector를 말하고 Stage 2는 Disk에 접근해서 커널을 로딩하는 파트를 말합니다.

  • Stage 1에서는 간단히 몇가지 BIOS 관련 Parameter를 설정하고 Stage 2로 넘어감
  • Stage 2에서는 리눅스 커널 파일(vmlinuz)을 분석해서 리눅스 커널의 부트로더(Bootloader)를 0x90000 ~ 에 로딩하고, 압축된 커널과 커널의 압축을 푸는 부분을 0x100000(1MB) ~ 에 로딩
  • Stage 2 후에는 커널 부트로더를 뛰어 넘어 0x90200 위치로 Jump(?)

사실 마지막 부분에 대한 내용은 제가 제대로 확인을 못해서 좀 애매하긴한데, 한빛미디어에서 나온 리눅스 커널의 이해를 보면 LILO 시절부터 0x90200 어드레스로 Jump를 하고 있습니다. 사실 GRUB 역시 크게 다르지 않을 것이라 생각합니다만... 좀 더 시간을 갖고 확인할 필요가 있을 것 같네요. ^^;;;;

GRUB에 대한 자세한 내용은 The GRUB MBR을 참고하세요 ;)

2. 리눅스 커널 부트로더

리눅스 커널 부트로더는 arch/x86/boot/header.S에 있는데요, 크기가 2 Sector입니다. ^^;;; 따라서 앞부분의 512Byte는 MBR 역할을 하고 그 뒤에 512Byte는 커널 부트로더와 커널을 이어주는 역할을 합니다.

그런데... 예전에는 바로 부팅할 수 있도록 했나 본데, 지금은 LILO나 GRUB의 등장으로 지원하지 않는 것 같습니다. 아래는 header.S에 있는 코드의 일부분인데요, 보시면 플로피를 통한 직접부팅을 지원 안하니까 다시 디스켓 빼라는 메시지와 함께 재부팅하는 코드만 달랑 있습니다. ^^;;;; 사실 이 부분의 코드가 GRUB에 의해 0x90000에 로딩되는 부분입니다.

 18 #include <asm/segment.h>
 19 #include <linux/utsrelease.h>
 20 #include <asm/boot.h>
 21 #include <asm/e820.h>
 22 #include <asm/page_types.h>
 23 #include <asm/setup.h>
 24 #include "boot.h"
 25 #include "offsets.h"
 26 
 27 BOOTSEG         = 0x07C0                /* original address of boot-sector */
 28 SYSSEG          = 0x1000                /* historical load address >> 4 */
 29 
 30 #ifndef SVGA_MODE
 31 #define SVGA_MODE ASK_VGA
 32 #endif
 33 
 34 #ifndef RAMDISK
 35 #define RAMDISK 0
 36 #endif
 37 
 38 #ifndef ROOT_RDONLY
 39 #define ROOT_RDONLY 1
 40 #endif
 41 
 42         .code16
 43         .section ".bstext", "ax"
 44 
 45         .global bootsect_start
 46 bootsect_start:
 47 
 48         # Normalize the start address
 49         ljmp    $BOOTSEG, $start2
 50 
 51 start2:
 52         movw    %cs, %ax
 53         movw    %ax, %ds
 54         movw    %ax, %es
 55         movw    %ax, %ss
 56         xorw    %sp, %sp
 57         sti
 58         cld
 59 
 60         movw    $bugger_off_msg, %si
 61 
 62 msg_loop:
 63         lodsb
 64         andb    %al, %al
 65         jz      bs_die             // <<= 무조건 Jump하면 메시지 출력후 bs_die로 이동~!!
 66         movb    $0xe, %ah
 67         movw    $7, %bx
 68         int     $0x10
 69         jmp     msg_loop
 70 
 71 bs_die:
 72         # Allow the user to press a key, then reboot
 73         xorw    %ax, %ax
 74         int     $0x16
 75         int     $0x19
 76 
 77         # int 0x19 should never return.  In case it does anyway,
 78         # invoke the BIOS reset code...
 79         ljmp    $0xf000,$0xfff0
 80 
 81         .section ".bsdata", "a"
 82 bugger_off_msg:
 83         .ascii  "Direct booting from floppy is no longer supported.\r\n"
 84         .ascii  "Please use a boot loader program instead.\r\n"
 85         .ascii  "\n"
 86         .ascii  "Remove disk and press any key to reboot . . .\r\n"
 87         .byte   0
 88 
 89 
 90         # Kernel attributes; used by setup.  This is part 1 of the
 91         # header, from the old boot sector.
 92 
 93         .section ".header", "a"
 94         .globl  hdr

이 바로 뒤는 당연히 0x90200에 위치하는데요, _start로 시작하며 커널의 bss 영역을 초기화하고 main 함수로 Jump하는 부분이 있습니다. 해당 부분의 코드는 arch/x86/boot/header.S를 참고하세요 ;)

104         # offset 512, entry point
105 
106         .globl  _start
107 _start:
108                 # Explicitly enter this as bytes, or the assembler
109                 # tries to generate a 3-byte jump here, which causes
110                 # everything else to push off to the wrong offset.
111                 .byte   0xeb            # short (2-byte) jump
112                 .byte   start_of_setup-1f
113 1:
114 
115         # Part 2 of the header, from the old setup.S

                             ... 생략 ...

224         .section ".inittext", "ax"
225 start_of_setup:
226 #ifdef SAFE_RESET_DISK_CONTROLLER
227 # Reset the disk controller.
228         movw    $0x0000, %ax            # Reset disk controller
229         movb    $0x80, %dl              # All disks
230         int     $0x13
231 #endif
232 
                             ... 생략 ...
271 
272 # Check signature at end of setup
273         cmpl    $0x5a5aaa55, setup_sig
274         jne     setup_bad
275 
276 # Zero the bss
277         movw    $__bss_start, %di
278         movw    $_end+3, %cx
279         xorl    %eax, %eax
280         subw    %di, %cx
281         shrw    $2, %cx
282         rep; stosl
283 
284 # Jump to C code (should not return)
285         calll   main       // <== 여기가 다음 스텝인 리눅스 커널 로더로 jump하는 부분

3. 리눅스 커널 로더

리눅스 커널 로더는 arch/x86/boot/main.c에 있습니다. 제가 이 코드에 리눅스 "리눅스 커널로더"라는 명칭을 사용해도 될지 모르겠지만, 일단 이 시점에서 32bit 모드로 변환하는 코드가 들어 있어서 커널로더라고 했습니다. ^^;;; 이 코드의 역할은 32bit 커널이 실행될 준비를 하는 건데요, BIOS를 통해 메모리 크기를 확인하고 키보드를 초기화한다음 32bit 커널로 Jump합니다. 이때 Jump하는 어드레스는 32bit 커널이 로딩되어 있는 0x100000(1MB)입니다. ^^;;; 해당 위치에 GRUB이 이미 올려놓았거든요 ;)

아래는 arch/x86/boot/main.c 파일의 일부분입니다. 좀더 자세한 코드는 arch/x86/boot/main.c를 통해서 확인할 수 있습니다. ^^;;;

125 void main(void)
126 {
127         /* First, copy the boot header into the "zeropage" */
128         copy_boot_params();
129 
130         /* End of heap check */
131         init_heap();
132 
133         /* Make sure we have all the proper CPU support */
134         if (validate_cpu()) {
135                 puts("Unable to boot - please use a kernel appropriate "
136                      "for your CPU.\n");
137                 die();
138         }
139 
140         /* Tell the BIOS what CPU mode we intend to run in. */
141         set_bios_mode();
142 
143         /* Detect memory layout */
144         detect_memory();
145 
146         /* Set keyboard repeat rate (why?) */
147         keyboard_set_repeat();
148 
149         /* Query MCA information */
150         query_mca();
151 
152         /* Query Intel SpeedStep (IST) information */
153         query_ist();
154 
155         /* Query APM information */
156 #if defined(CONFIG_APM) || defined(CONFIG_APM_MODULE)
157         query_apm_bios();
158 #endif
159 
160         /* Query EDD information */
161 #if defined(CONFIG_EDD) || defined(CONFIG_EDD_MODULE)
162         query_edd();
163 #endif
164 
165         /* Set the video mode */
166         set_video();
167 
168         /* Parse command line for 'quiet' and pass it to decompressor. */
169         if (cmdline_find_option_bool("quiet"))
170                 boot_params.hdr.loadflags |= QUIET_FLAG;
171 
172         /* Do the last things and invoke protected mode */
173         go_to_protected_mode();    // <== 여기가 32bit 커널로 Jump하는 부분이에요 :)
174 }

4. 리눅스 커널 - 압축된 커널

커널 로더에 의해서 32bit 커널로 jmp(0x100000)하면 이제 본격적으로 커널이 시작되는데요, 이때 수행되는 함수는 arch/x86/boot/compressed/head_32.S에 있는 가장 첫 번째 함수, startup_32입니다. 파일의 가장 앞부분에 있는 함수기 때문에 0x100000 어드레스로 Jump하는 것만으로 바로 저 함수에 도달하는 것이지요. ^^;;;

startup_32() 함수는 여러가지 일을 하는데요, 가장 중요한 역할은 자신에게 붙어있는 압축된 커널을 안전한 버퍼로 옮긴다음 압축을 푸는 겁니다. >ㅁ<)-b 그리고 압축을 풀고나면 이를 다시 0x400000 어드레스로 옮기고 jump하는 것이지요. 안전한 버퍼라는 말이 조금 애매하긴한데 대략 0x400000 어드레스 부근인 것 같습니다(정확하게는 _end 오프셋 뒷쪽인데요, 이 값은 빌드 타임에 정해지는 값이라 compressed 디렉터리의 코드량에 따라서 좀 달라질 수도 있을 것 같습니다. ^^;;; (정확하지는 않아요 ㅠㅠ)

다음은 압축된 커널을 안전한 곳으로 복사하고 압축을 푼 뒤에 압축이 해제된 곳으로 jump하는 코드입니다. 전체 내용은 arch/x86/boot/compressed/head_32.S를 참조하세요.


 26 #include <linux/linkage.h>
 27 #include <asm/segment.h>
 28 #include <asm/page_types.h>
 29 #include <asm/boot.h>
 30 #include <asm/asm-offsets.h>
 31 
 32 .section ".text.head","ax",@progbits
 33 ENTRY(startup_32)
 34         cld
 35         /* test KEEP_SEGMENTS flag to see if the bootloader is asking
 36          * us to not reload segments */
 37         testb $(1<<6), BP_loadflags(%esi)
 38         jnz 1f
 39 
 40         cli
 41         movl $(__BOOT_DS),%eax
 42         movl %eax,%ds
 43         movl %eax,%es
 44         movl %eax,%fs
 45         movl %eax,%gs
 46         movl %eax,%ss
 47 1:
                     ... 생략 ...

 87 /* Copy the compressed kernel to the end of our buffer
 88  * where decompression in place becomes safe.
 89  */
 90         pushl %esi
 91         leal _end(%ebp), %esi
 92         leal _end(%ebx), %edi
 93         movl $(_end - startup_32), %ecx
 94         std
 95         rep
 96         movsb
 97         cld
 98         popl %esi
 99 
100 /* Compute the kernel start address.
101  */
102 #ifdef CONFIG_RELOCATABLE
103         addl    $(CONFIG_PHYSICAL_ALIGN - 1), %ebp
104         andl    $(~(CONFIG_PHYSICAL_ALIGN - 1)), %ebp
105 #else
106         movl    $LOAD_PHYSICAL_ADDR, %ebp
107 #endif
108 
109 /*
110  * Jump to the relocated address.
111  */
112         leal relocated(%ebx), %eax
113         jmp *%eax
114 ENDPROC(startup_32)
115 
116 .section ".text"
117 relocated:
118 
119 /*
120  * Clear BSS
121  */
122         xorl %eax,%eax
123         leal _edata(%ebx),%edi
124         leal _end(%ebx), %ecx
125         subl %edi,%ecx
126         cld
127         rep
128         stosb
129 
130 /*
131  * Setup the stack for the decompressor
132  */
133         leal boot_stack_end(%ebx), %esp
134 
135 /*
136  * Do the decompression, and jump to the new kernel..
137  */
138         movl output_len(%ebx), %eax
139         pushl %eax
140                         # push arguments for decompress_kernel:
141         pushl %ebp      # output address
142         movl input_len(%ebx), %eax
143         pushl %eax      # input_len
144         leal input_data(%ebx), %eax
145         pushl %eax      # input_data
146         leal boot_heap(%ebx), %eax
147         pushl %eax      # heap area
148         pushl %esi      # real mode pointer
149         call decompress_kernel       <<= 요기가 실제로 압축 해제를 담당하는 부분
150         addl $20, %esp
151         popl %ecx
152 
153 #if CONFIG_RELOCATABLE
154 /* Find the address of the relocations.
155  */
156         movl %ebp, %edi
157         addl %ecx, %edi
158 
159 /* Calculate the delta between where vmlinux was compiled to run
160  * and where it was actually loaded.
161  */
162         movl %ebp, %ebx
163         subl $LOAD_PHYSICAL_ADDR, %ebx
164         jz   2f         /* Nothing to be done if loaded at compiled addr. */
165 /*
166  * Process relocations.
167  */
168 
169 1:      subl $4, %edi
170         movl 0(%edi), %ecx
171         testl %ecx, %ecx
172         jz 2f
173         addl %ebx, -__PAGE_OFFSET(%ebx, %ecx)
174         jmp 1b
175 2:
176 #endif
177 
178 /*
179  * Jump to the decompressed kernel.
180  */
181         xorl %ebx,%ebx
182         jmp *%ebp                   // <<= 압축을 푼 어드레스로 jump하는 부분

실제로 압축 해제는 decompress_kernel() 함수를 호출해서 처리하는데, 이 함수는 arch/x86/boot/compressed/misc.c 파일에 있습니다. decompress_kernel() 함수의 원형은 아래와 같습니다.

304 asmlinkage void decompress_kernel(void *rmode, memptr heap,
305                                   unsigned char *input_data,
306                                   unsigned long input_len,
307                                   unsigned char *output)

위의 함수 원형을 보면 결국 우리가 넘겨주는 output buffer에 차곡차곡 압축을 풀어줌을 알 수 있는데요, 저 output 버퍼의 위치는 decompress_kernel()을 호출하는 곳에서 LOAD_PHYSICAL_ADDR로 대략 정의하고 있는데요, 정확하게 말하기는 힘들지만 대략 0x400000(4MB) 어드레스 부근이라고 생각됩니다.

리눅스 커널의 이해를 보면 압축을 해제한 뒤에 다시 1MB 영역의 어드레스로 덮어쓴다는 이야기가 있던데, 실제 코드에서는 바로 jump하는 부분만 확인했을 뿐 데이터를 이동하는 부분은 찾지 못했습니다. ㅠㅠ 그렇다는 이야기는 이제는 더이상 안옮기거나 제가 못찾았다는 이야기겠지요. 혹시 찾으신 분 있으시면 덧글로 제보 부탁드릴께요 ㅠㅠ

5. 리눅스 커널 - 실제 커널

이제 압축까지 다 해제된 실제 커널로 왔습니다. 커널의 실제 엔트리 포인트는 startup_32() 함수이구요, arch/x86/kernel/head_32.S에 있습니다. 이 함수를 보면 페이지 테이블을 임시로 설정하고 start_kernel() 함수를 호출하는 것을 볼 수 있습니다. start_kernel()은 32bit 커널에서 C 코드가 시작되는 부분이지요. 여기까지만 오면 사실 부팅은 거의 끝난 겁니다. ^^;;;;

다음은 startup_32() 함수의 코드입니다. 전체 코드는 arch/x86/kernel/head_32.S 파일을 참조하세요.

269 #ifdef CONFIG_SMP
270 ENTRY(startup_32_smp)
271         cld
272         movl $(__BOOT_DS),%eax
273         movl %eax,%ds
274         movl %eax,%es
275         movl %eax,%fs
276         movl %eax,%gs
277 #endif /* CONFIG_SMP */

             ... 생략 ...

451         movl $(__KERNEL_STACK_CANARY),%eax
452         movl %eax,%gs
453 
454         xorl %eax,%eax                  # Clear LDT
455         lldt %ax
456 
457         cld                     # gcc2 wants the direction flag cleared at all times
458         pushl $0                # fake return address for unwinder
459 #ifdef CONFIG_SMP
460         movb ready, %cl
461         movb $1, ready
462         cmpb $0,%cl             # the first CPU calls start_kernel
463         je   1f
464         movl (stack_start), %esp
465 1:
466 #endif /* CONFIG_SMP */
467         jmp *(initial_code)     // <<= start_kernel() 함수의 어드레스가 들어 있음
468 

6. 리눅스 커널 - C 함수 시작

start_kernel() 함수는 init/main.c 파일에 있으며, 각종 커널 관련 자료구조(페이지 테이블 포함)를 다시 한번 초기화하고 init 프로세스를 생성하는 역할을 합니다. start_kernel() 함수의 가장 마지막은 rest_init() 함수인데요, 이 함수는 kernel_thread() 함수를 호출해서 별도의 스레드를 만든 다음 kernel_init 함수를 수행하게 만듭니다. 그리고 자기 자신은 계속 코드를 수행하면서 cpu_idle() 함수를 수행하여 idle process가 됩니다.

다음은 start_kernel() 함수의 일부분과 rest_init() 함수의 일부분을 나타낸 것입니다.

535 
536 asmlinkage void __init start_kernel(void)
537 {
538         char * command_line;
539         extern struct kernel_param __start___param[], __stop___param[];
540 
541         smp_setup_processor_id();
542 
543         /*
544          * Need to run as early as possible, to initialize the
545          * lockdep hash:
546          */
547         lockdep_init();
548         debug_objects_early_init();
549 
550         /*
551          * Set up the the initial canary ASAP:
552          */
553         boot_init_stack_canary();
554 
555         cgroup_init_early();
556 
557         local_irq_disable();
558         early_boot_irqs_off();
559         early_init_irq_lock_class();
560 
561 /*
562  * Interrupts are still disabled. Do necessary setups, then
563  * enable them
564  */
565         lock_kernel();
566         tick_init();
567         boot_cpu_init();
568         page_address_init();
569         printk(KERN_NOTICE "%s", linux_banner);
570         setup_arch(&command_line);
571         mm_init_owner(&init_mm, &init_task);
572         setup_command_line(command_line);
573         setup_per_cpu_areas();
574         setup_nr_cpu_ids();
575         smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */
576 
577         /*
578          * Set up the scheduler prior starting any interrupts (such as the
579          * timer interrupt). Full topology setup happens at smp_init()
580          * time - but meanwhile we still have a functioning scheduler.
581          */
582         sched_init();
583         /*
584          * Disable preemption - early bootup scheduling is extremely
585          * fragile until we cpu_idle() for the first time.
586          */
587         preempt_disable();
588         build_all_zonelists();
589         page_alloc_init();
590         printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line);
591         parse_early_param();
592         parse_args("Booting kernel", static_command_line, __start___param,
593                    __stop___param - __start___param,
594                    &unknown_bootoption);
595         if (!irqs_disabled()) {
596                 printk(KERN_WARNING "start_kernel(): bug: interrupts were "
597                                 "enabled *very* early, fixing it\n");
598                 local_irq_disable();
599         }
600         sort_main_extable();
601         trap_init();
602         rcu_init();
603         /* init some links before init_ISA_irqs() */
604         early_irq_init();
605         init_IRQ();
606         pidhash_init();
607         init_timers();
608         hrtimers_init();
609         softirq_init();
610         timekeeping_init();
611         time_init();
612         sched_clock_init();
613         profile_init();
614         if (!irqs_disabled())
615                 printk(KERN_CRIT "start_kernel(): bug: interrupts were "
616                                  "enabled early\n");
617         early_boot_irqs_on();
618         local_irq_enable();
619 
620         /*
621          * HACK ALERT! This is early. We're enabling the console before
622          * we've done PCI setups etc, and console_init() must be aware of
623          * this. But we do want output early, in case something goes wrong.
624          */
625         console_init();
626         if (panic_later)
627                 panic(panic_later, panic_param);
628 
629         lockdep_info();
630 
631         /*
632          * Need to run this when irqs are enabled, because it wants
633          * to self-test [hard/soft]-irqs on/off lock inversion bugs
634          * too:
635          */
636         locking_selftest();
637 
638 #ifdef CONFIG_BLK_DEV_INITRD
639         if (initrd_start && !initrd_below_start_ok &&
640             page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
641                 printk(KERN_CRIT "initrd overwritten (0x%08lx < 0x%08lx) - "
642                     "disabling it.\n",
643                     page_to_pfn(virt_to_page((void *)initrd_start)),
644                     min_low_pfn);
645                 initrd_start = 0;
646         }
647 #endif
648         vmalloc_init();
649         vfs_caches_init_early();
650         cpuset_init_early();
651         page_cgroup_init();
652         mem_init();
653         enable_debug_pagealloc();
654         cpu_hotplug_init();
655         kmem_cache_init();
656         kmemtrace_init();
657         debug_objects_mem_init();
658         idr_init_cache();
659         setup_per_cpu_pageset();
660         numa_policy_init();
661         if (late_time_init)
662                 late_time_init();
663         calibrate_delay();
664         pidmap_init();
665         pgtable_cache_init();
666         prio_tree_init();
667         anon_vma_init();
668 #ifdef CONFIG_X86
669         if (efi_enabled)
670                 efi_enter_virtual_mode();
671 #endif
672         thread_info_cache_init();
673         cred_init();
674         fork_init(num_physpages);
675         proc_caches_init();
676         buffer_init();
677         key_init();
678         security_init();
679         vfs_caches_init(num_physpages);
680         radix_tree_init();
681         signals_init();
682         /* rootfs populating might need page-writeback */
683         page_writeback_init();
684 #ifdef CONFIG_PROC_FS
685         proc_root_init();
686 #endif
687         cgroup_init();
688         cpuset_init();
689         taskstats_init_early();
690         delayacct_init();
691 
692         check_bugs();
693 
694         acpi_early_init(); /* before LAPIC and SMP init */
695 
696         ftrace_init();
697 
698         /* Do the rest non-__init'ed, we're now alive */
699         rest_init();               // <<= 요기가 실제 init 프로세스 생성으로 가는 부분
700 }

아래는 rest_init() 함수의 내용입니다. 

451 static noinline void __init_refok rest_init(void)
452         __releases(kernel_lock)
453 {
454         int pid;
455 
            // init 프로세스를 생성하는 kernel_init 스레드를 만드는 부분
456         kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
457         numa_default_policy();
458         pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
459         kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
460         unlock_kernel();
461 
462         /*
463          * The boot idle thread must execute schedule()
464          * at least once to get things moving:
465          */
466         init_idle_bootup_task(current);
467         rcu_scheduler_starting();
468         preempt_enable_no_resched();
469         schedule();
470         preempt_disable();
471 
472         /* Call into cpu_idle with preempt disabled */
473         cpu_idle();              // <<= Idle Process가 되는 부분
474 }

kernel_thread()는 아래 코드에서 볼 수 있듯이 마지막에 init_post() 함수를 호출하는데 이 함수가 최종적으로 init process를 생성합니다. 다음은 kernel_thread() 함수와 init_post() 함수의 코드입니다. 리눅스가 실행될 때 일반적으로 initrd와 같은 RAMDISK 이미지를 사용하기 때문에, init_post() 함수 안의 ramdisk_execute_command 변수에는 "/init" 이 들어가 있으며, 따라서 initrd의 루트 디렉터리의 init 프로세스를 실행합니다.

initrd에 있는 init은 사실 스크립트로 기본적인 드라이버를 로딩하고 루트 디스크를 마운트한 뒤, 실제 init process로 제어를 넘깁니다. 그러면 본격적으로 커널이 실행되는 것이죠. ^^;;;;

847 static int __init kernel_init(void * unused)
848 {
849         lock_kernel();
850         /*
851          * init can run on any cpu.
852          */
853         set_cpus_allowed_ptr(current, cpu_all_mask);
854         /*
855          * Tell the world that we're going to be the grim
856          * reaper of innocent orphaned children.
857          *
858          * We don't want people to have to make incorrect
859          * assumptions about where in the task array this
860          * can be found.
861          */
862         init_pid_ns.child_reaper = current;
863 
864         cad_pid = task_pid(current);
865 
866         smp_prepare_cpus(setup_max_cpus);
867 
868         do_pre_smp_initcalls();
869         start_boot_trace();
870 
871         smp_init();
872         sched_init_smp();
873 
874         do_basic_setup();
875 
876         /*
877          * check if there is an early userspace init.  If yes, let it do all
878          * the work
879          */
880 
881         if (!ramdisk_execute_command)
882                 ramdisk_execute_command = "/init";
883 
884         if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
885                 ramdisk_execute_command = NULL;
886                 prepare_namespace();
887         }
888 
889         /*
890          * Ok, we have completed the initial bootup, and
891          * we're essentially up and running. Get rid of the
892          * initmem segments and start the user-mode stuff..
893          */
894 
895         init_post();       // <<= init process 생성으로 가는 부분
896         return 0;
897 }

803 static noinline int init_post(void)
804         __releases(kernel_lock)
805 {
806         /* need to finish all async __init code before freeing the memory */
807         async_synchronize_full();
808         free_initmem();
809         unlock_kernel();
810         mark_rodata_ro();
811         system_state = SYSTEM_RUNNING;
812         numa_default_policy();
813 
814         if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
815                 printk(KERN_WARNING "Warning: unable to open an initial console.\n");
816 
817         (void) sys_dup(0);
818         (void) sys_dup(0);
819 
820         current->signal->flags |= SIGNAL_UNKILLABLE;
821 
822         if (ramdisk_execute_command) {   // <<= initrd와 같이 RAMDISK 이미지를 사용하는 경우 이 코드로 진입하고, 이때 ramdisk_execute_command는 /init이 들어 있음
823                 run_init_process(ramdisk_execute_command);
824                 printk(KERN_WARNING "Failed to execute %s\n",
825                                 ramdisk_execute_command);
826         }
827 
828         /*
829          * We try each of these until one succeeds.
830          *
831          * The Bourne shell can be used instead of init if we are
832          * trying to recover a really broken machine.
833          */
834         if (execute_command) {
835                 run_init_process(execute_command);
836                 printk(KERN_WARNING "Failed to execute %s.  Attempting "
837                                         "defaults...\n", execute_command);
838         }
839         run_init_process("/sbin/init");
840         run_init_process("/etc/init");
841         run_init_process("/bin/init");
842         run_init_process("/bin/sh");
843 
844         panic("No init found.  Try passing init= option to kernel.");
845 }

start_kernel() 함수 이하에 있는 함수들의 전체 코드는 init/main.c를 참고하기 바랍니다. ^^

약간 보기 불편한 점은 있지만 그래도 한번 정리해놓으니 좋군요.
그럼 다들 좋은 밤 되세요 ;)

참고 자료

근 한달동안 모든 코딩을 어셈블리어로만 하고 있다보니 호출 규약(Calling Convention)을 다시 살펴보고 있습니다. C로 프로그래밍하던 시절에는 호출 규약은 별로 신경쓰지 않았는데, 어셈블리어로 함수를 호출하려니 안 볼수가 없더군요. ㅠㅠ


MINT64 OS를 만들 때 어셈블리어로 코딩했으면서 왠 엄살이냐고 생각하실텐데... 리눅스에서 사용하는 호출 규약(Calling Convention)과 윈도우에서 사용하는 호출 규약은 상당한 차이가 있더라구요. ㅠㅠ 그래서 적응하는데 살짝 힘들었습니다. 사실 적응한다기 보다는 차이를 아는데 시간이 좀 걸렸지요. ㅠㅠ

64비트 리눅스의 호출 규약

리눅스는 64비트 모드에서 파라미터를 전달할 때 윈도우보다 레지스터를 더 많이 사용합니다. 정수 타입의 파라미터를 전달할 때는 순서대로 RDI, RSI, RDX, RCX, R8, R9까지 6개의 레지스터를 사용하고 7개 이상이면 스택을 통해 전달합니다. 실수 타입의 파라미터의 경우는 XMM0 ~ XMM7까지 8개를 순서대로 사용하고 그 이상이면 스택으로 전달하지요. ;)


반환값은 정수일 때 RAX(하위 64비트), RDX(상위 64비트)를 사용하고, 실수일때는 XMM0(하위 128비트), XMM1(상위 128비트)를 사용합니다. 아래는 64비트 멀티코어 OS 원리와 구조의 11.2.2 장에서 추출한 호출 규약 그림입니다. ;)




64비트 윈도우의 호출 규약

반면, 윈도우는 64비트 모드에서 파라미터를 전달할 때 레지스터 4개만 사용합니다. 정수 타입의 경우는 순서대로 RCX, RDX, R8, R9를 사용하고 나머지는 스택으로 전달합니다. 실수의 경우는 XMM0 ~ XMM3까지 4개를 순서대로 사용하며 나머지는 스택으로 전달합니다. 여기까지 보면 파라미터로 사용하는 레지스터의 종류와 개수만 차이가 나는 것 같습니다만.... 실제로 보면 스택을 사용하는 방법도 차이가 있습니다.


리눅스의 경우 파라미터를 전달할 때 스택을 꽉꽉 채워서 사용하는 반면, 윈도우의 경우는 4개의 레지스터가 들어갈 공간만큼을 띄워서 사용합니다. 즉, 파라미터 4개가 RCX, RDX, R8, R9를 통해 전달되지만, 스택에 이 4개를 위한 공간을 할당해놓는 것이죠. 아래 그림을 보시면 좀 더 이해하시기 편할 겁니다. ^^;;;





이 차이 때문에 한참을 헤맸네요. ㅠㅠ 스택에 저 공간을 안 할당해놓으면 파라미터가 잘못 전될되서 보기좋게 함수 호출이 실패를... 쿨럭..;;; 그리고 주의할 점은 비록 파라미터가 4개 미만이더라도 함수 호출 시 저 영역은 무조건 할당해야 한다는 겁니다. 에궁... 진짜 알고나면 별 것 아닌데... 코드를 몇 번이나 갈아 엎었는지 모르겠네요. ㅠㅠ


C로 코딩을 했으면 아예 신경을 안써도 되는 부분인데... 어셈블리어로 코딩하다보니 삽질을 하고 말았군요. ㅎㅎ 윈도우 호출 규약에 대한 보다 자세한 내용이 궁금하시다면 MSDN 사이트를 참고하시기 바랍니다. ^^


그나저나 PE32 파일 포멧을 읽어 메모리에 로딩하여 실행하는 가볍게 어셈블리어로 작성하는 괴수(?)를 어떻게해야 따라잡을 수 있을까요? 아우... 따라가려니 죽을 것만 같네요. ㅠㅠ


그럼 좋은 밤 되세요 ;)

헉... 오늘은 참 재미난 것(?)들을 많이 보게 되는군요. 이번에는 iPhone에 Linux를 올린 동영상입니다. 이건 말이 필요없습니다. 일단 한번 보시죠.


iPhone Linux Demonstration Video from planetbeing on Vimeo.

와우~ 콘솔 쉘이 뜨고 리눅스 커맨드를 입력하는 모습이 참 인상적이네요. 부트 로더를 만들어 올려서 iPhone과 Linux를 선택해서 돌릴 수 있게 한 것 같은데... 정말 대단한 사람들인 것 같습니다. ^^;;;

iPhone용 Linux에 대한 내용은 http://linuxoniphone.blogspot.com/2008/11/linux-on-iphone.html 에서 보실 수 있습니다. iPhone이랑 iPod 터치랑 크게 차이가 없으니 iPod 터치에도 리눅스를 돌릴 수 있겠다는 생각이 드네요. ^^;;; 나중에 시간있으면 하나 질러서 뭐 좀 해봐야겠습니다. 그러고 보니 한참 가지고 놀던 NDS가 저쪽에 처박혀서 절 째려보고 있군요. ㅠㅠ 아흙... NDS에 프로그램도 업데이트 해야하는데... 시간이 부족하네요. ㅎㅎ

여튼... 세상에는 참 대단한 사람들이 많다는 거~!!!!

 오늘은 즐거운(??) 예비군 훈련날이라 야근없이 바로 집에왔습니다. 그리고 밥 한그릇 빨리 뚝딱하고 ELF64 파일 포맷에 대한 문서를 작성하기 시작했습니다. 사실 시작은 어제부터 했지만, 본격적으로 시작한 건 오늘부터라서... ^^;;;;

 문서는 Relocation 방법에 대해서 초점이 맞춰져 있지만, 기반 설명을 위해 앞쪽에 ELF64 파일 포멧에 대한 내용을 넣는게 좋겠다는 생각이 들더군요. 그래서 스펙 문서를 긁어 붙이면서 설명 첨부하는 식으로 간단히 넘어갔습니다.

 그런데 그 동안 테스트 프로그램으로 ELF64를 분석할때는 별 문제 없는 것 같던 Relocation Type이라는 값이, 문서를 작성하려고 보니까 뭔가 이상한겁니다. ㅠㅠ 제가 보고 있는 ELF64 문서의 버전이 낮아서 그런지 타입에 대한 설명을 건너뛰었더군요. 그래서 ELF32에 문서쪽을 찾아보니  32bit 기준으로 설정되어 있어서, 뭔가 일치하지 않는 부분이 있었습니다. 한가지 예를 들면 objdump.exe로 Relocation Entry를 읽으면 Relocation Type이 0x0B인 것이 나오는데, ELF32에는 0x0A까지만 정의되어 있더군요. ㅡ_ㅡ;;;;;;

 도저히 이건 아니다 싶어서 다시 구글 검색을 시작했습니다. 결국 AMD쪽 사이트에서 문서를 구했습니다. 생각보다 자료를 찾기가 쉽지 않네요. 이런쪽을 파보는 사람이 별로 없어서 그런가... ㅡ_ㅡa... 앞으로 테스트 프로그램을 짤때도 정리를 꼭 해야겠습니다. 정리를 안하니 그냥 아는 척(??) 하고 넘어가게 되네요. ㅠㅠ



 이런 이유로... 오늘 하루종일 결국 삽질만 하고 정녕 Relocation 처리는 완료하지 못했습니다. 크윽... 내일은 금요일이니 좀 신경써서 마무리를 하고 64Bit Test OS에 올려봐야겠네요. ;)

 그럼 다들 좋은 밤 되시길~ ;)

ps) 어디 고정 폭 폰트 중에서 저작권 없는 깜찍한 폰트 없나요? ㅠㅠ

정 안되면 폰트 홍보해주겠다고 하고 저작권료를 안주는 방법을 고민해봐야할지도... ㅠㅠ

살려주세요~!!! ㅠㅠ



 운동을 갔다 오니 벌써 세미나가 시작되서 후다닥 들어갔더니 한 20분치를 날려먹었습니다.ㅜ_ㅜ 세미나 내용은 SE Linux를 이용해서 좀더 보안에 강한 구조로 만드는 것인데, 일단 ARM 기반의 임베디드 리눅스라는 점과 커널과 파일 시스템을 직접 손봐야 한다는 점에서 난이도가 좀 있었지요. ^^;;;

 아무 생각없이 세미나 자료를 죽 훓어보고 있었는데, 오~!!! 나름대로 준비를 좀 했습니다. 솔찍히 말하자면 스택과 힙 오버플로우를 이용한 쉘 획득은 거의 막은 듯 보였습니다. 물론 제가 리눅스를 잘 모르기때문에 다른 방법이 있는지는 잘 모르겠지만, 커널쪽의 API를 수정해서 프로세스 실행 쪽을 모니터링하고 있더군요.

 자세한 로직은 공개하기가 힘들지만 딱 보기에도 이렇게 하면 마음대로 실행이 안되겠구나 하는 생각이 들었습니다. 커널을 수정하고 테스트하는 것이 쉬운 방법이 아닐텐데 용케 했더군요.

 파일 시스템도 JFFS2를 수정해서 파일별로 권한 정보 및 인증 정보를 따로 남기도록 해놨습니다. 따라서 커널이 프로그램을 실행할 때 그 정보를 이용해서 실행파일이 변조되지 않은 유효한 것인지 그리고 실행할 수 있는 것인지를 처리하더군요. 그 외에도 많은 기능이 있는데 일일이 나열하면 왠지 안될 것 같아서 이만 줄입니다. ^^;;;

 제가 관심이 있는 분야가 이쪽이라서 그런지 아주 흥미진진하더라구요. 후배들 덕에 공부 한번 제대로 했습니다. 후배들 화이팅~!!! ^ㅡ^)/~

+ Recent posts