unlink라는 시스템 콜을 따라가면 아래 함수를 볼 수 있다.
여기서 lookup_hash(&nd) 함수를 좀 파고들어가 보자.

static long do_unlinkat(int dfd, const char __user *pathname)
{
    int error;
    char *name;
    struct dentry *dentry;
    struct nameidata nd;
    struct inode *inode = NULL;
...
    nd.flags &= ~LOOKUP_PARENT;

    mutex_lock_nested(&nd.path.dentry->d_inode->i_mutex, I_MUTEX_PARENT);
    dentry = lookup_hash(&nd);
}

아래와 같은 함수 호출 체인을 볼 수 있는데, 덴트리는 덴트리 캐시에서 덴트리 인스턴스를 찾는 것이다.
static struct dentry *lookup_hash(struct nameidata *nd)
{
    return __lookup_hash(&nd->last, nd->path.dentry, nd);
}

static struct dentry *__lookup_hash(struct qstr *name,
        struct dentry *base, struct nameidata *nd)
{
    bool need_lookup;
    struct dentry *dentry;

    dentry = lookup_dcache(name, base, nd, &need_lookup);
    if (!need_lookup)
        return dentry;

    return lookup_real(base->d_inode, dentry, nd);
}

d_hash(parent, hash); 라는 함수를 호출해서 해시 테이블 포멧의 덴트리 캐시 리스트를 가져온다.

static struct dentry *lookup_dcache(struct qstr *name, struct dentry *dir,
                    struct nameidata *nd, bool *need_lookup)
{
    struct dentry *dentry;
    int error;

    *need_lookup = false;
    dentry = d_lookup(dir, name);
..
}
struct dentry *d_lookup(struct dentry *parent, struct qstr *name)
{
    struct dentry *dentry;
    unsigned seq;

        do {
                seq = read_seqbegin(&rename_lock);
                dentry = __d_lookup(parent, name);
                if (dentry)
            break;
    } while (read_seqretry(&rename_lock, seq));
    return dentry;
}

struct dentry *__d_lookup(struct dentry *parent, struct qstr *name)
{
    unsigned int len = name->len;
    unsigned int hash = name->hash;
    const unsigned char *str = name->name;
    struct hlist_bl_head *b = d_hash(parent, hash);
    struct hlist_bl_node *node;
..
}

결국 *d_hash() 함수를 통해 덴트리 캐시를 처리하는 해시 테이블에 접근한다.
dentry_hashtable은 0xC17C8200에 있다.
  (static struct hlist_bl_head *) dentry_hashtable = 0xC17C8200 = __bss_stop+0x9117E0 -> (
    (struct hlist_bl_node *) first = 0x0 =  -> NULL)
static inline struct hlist_bl_head *d_hash(const struct dentry *parent,
                    unsigned int hash)
{
    hash += (unsigned long) parent / L1_CACHE_BYTES;
    hash = hash + (hash >> D_HASHBITS);
    return dentry_hashtable + (hash & D_HASHMASK);
}

0xC17C8200 란 주소와 이 리스트의 구조체만 가지고 리스트의 내용을 깔 수가 있는데 다음에 찾자.

이번에는 GPIO에 대해 이야기 좀 해보려 합니다.
 
한 10년 전인가요? 기억이 좀 가물 가물한데요. 대학원 면접을 봤었어요. 그 때 어느 교수님이 했던 질문이 생각납니다.
 
     "소프트웨어와 하드웨어의 경계가 어디냐"? 
 
그 당시 너무 얼어 있어 제대로 대답을 못했던 것 같습니다. 결국 대답을 못하고 말았습니다. 지금 생각해 보면 정말 쉬운 질문이었는데 참 안타깝죠.
 
만약 지금 그 질문을 받으면 전 이렇게 대답할 것입니다.
 
     "GPIO와 인터럽트입니다."
 
물론 하드웨어와 소프트웨어의 경계가 GPIO와 인터럽트만 있는 것은 아닙니다. 
메모리 맵드 아이오(memory mapped IO)도 있죠. 특정 메모리 공간에 어떤 값을 쓰면 이에 맞게 하드웨어 디바이스가 동작하는 것입니다.
 
이제 좀 GPIO에 대해서 조금 더 짚어보겠습니다.

보통 MCU에는 본연의 임무를 하는 pin들이 있습니다. 

     "Hardware적으로 정해져 있는 CS, WE라든가 하는 정해진 일을 하는 pin
    어떤 특별한 임무를 갖지 않고, 유저가 원하는 대로 I/O로 사용할 수 있는 pin" 

그런데, 재미있게도 GPIO란 핀은 여러가지 기능을 제공합니다. 다용도로 쓸 수 있다는 것입니다. 축구 선수를 스트라이커, 수비수, 골키퍼로 사용할 수 있는 것과 비슷한 것 같습니다.

좀 더 기술적으로 예를 들어볼까요? 
 
     "어떤 pin은 어떤 chip에 대한 CS로 사용할 수도 있지만, GPIO로도 쓸 수 있다."
 
 이것을 임베디드 용어로 Alternative Functionality라고 말합니다. 그런데 어떤 pin은 이런 Alternative Functionality를 갖는가 하면, GPIO 전용으로만 쓰이는 pin들이 있습니다. 이 정보를 제대로 알려면 MCU의 데이터 쉬트(Data Sheet)나 회로도를 꼭 읽어봐야 합니다.
 
이런 것을 GPIO라고 부르는데, GPIO라는 건 GP + I/O라는 뜻입니다. GPIO는 말 그대로 , IO니까, Input을 받거나(GPIO pin 상태를 읽고 싶거나), Output을 내놓거나(GPIO pin에 뭔가 쓰고 싶거나)하는 데 쓰입니다. 이런 GPIO는 MCU하나에 적으면 10개 많으면 200개까지 준비되어 있습니다.

ARM 기반 MCU입장에서 GPIO에 대해서 알아볼까요? 
 
     "GPIO는 AMBA bus에 연결된 SOC Peripheral이다." 
     "GPIO는 APB bus에 연결된 Slave이다. "
 
그래서 GPIO들도 Register를 가지고 있고, CPU입장에서는 Register를 통해서 control 가능합니다. 
 
그 제어(control)이라는 게 I/O로 쓸 수 있다고 했으니까, 어떤 때는 인풋(Input) 어떤 때는 아웃풋(Output)으로 써야 합니다. 이런 I/O는 레지스터(Register)를 통해서 Programmable할 수 있습니다. Input, Output으로 원하는 대로 사용 가능해야 하니까. 이런 레지스터의 구조는 물론 MCU마다 당연히 달라요. 퀄컴, 인텔, 엔비디아 서로 다른 구조로 설계를 했다는 것이지요. 그러니 data sheet를 잘 읽어야 합니다.

Register를 통해서 GPIO를 control해야 하는 것에는, 3가지가 있어요. 이 3가지가 모두 제대로 설정되어야 GPIO를 내 수족처럼 control할 수 있어요.
 
1) Pin의 Mode
   → GPIO로 쓸 꺼냐, Alternative Functionality로 쓸 꺼냐.
   → Alternative로 설정하게 되면 GPIO로는 못쓴다. 
   → 게임 오바 되면 Hardware적으로 정해진 임무를 수행. 신경 꺼도 됨

2) Pin의 상태 (Mode)를 활성화 하면서 Data Direction을 정할 수 있다.
   → 지금부터 Pin이 사용 가능하도록 Init 하겠다.
   → Input이냐, Output이냐를 결정하겠다.

3) 자~ 읽어 보자 라든가, 자~ 값을 써보자를 할 수 있다.

리눅스 커널 API

리눅스 드라이버 레벨에서 GPIO를 제어 하기 위해 리눅스 커널에서는 다음 API를 제공합니다.
필요한 헤더파일   #include <asm/gpio.h>
GPIO 입력설정     gpio_direction_input( gp_nr );
GPIO 출력설정     gpio_direction_ouput( gp_nr,  init_val );   // init_val 는 초기값이다.
GPIO 출력            gpio_set_value( gp_nr, val );                   // val 는 0, 1 값이다.
GPIO 입력            gpio_get_value( gp_nr );
GPIO 인터럽트 활성화   set_irq_type( irq_nr, irq_type );           
 
그런데, 이렇게 API에 대한 설명만 읽고는 실무에 활용할 수 없습니다. 상세한 시나리오를 만들어서 실제 API가 어디서 사용되는 지 알아야 머리에 오래 남고 다른 드라이버에 활용하기 쉽습니다.


GPIO 출력 모드

아래는 GPIO를 출력 모드로 설정해서 사용하고 싶을 때의 예시 코드입니다.
GPIO를 출력 모드로 사용한다는 것은 특정 상황에서 GPIO을 0 혹은 1로 바꾸고 싶을 경우에 주로 사용합니다.

(참고로 아래 udelay(1) 만큼 딜레이를 주는 코드가 칫셉 벤더에서 전달이 되면, 절대 바꾸면 안됩니다. 이게 다 데이터 시트에 있는 것을 구현하는 것이기 때문입니다.)
#define GPIO_NUMBER 11 
int gpio_nreset = GPIO_NUMBER;
 
static int cs4271_probe(struct snd_soc_codec *codec)
{
    struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec);
    struct cs4271_platform_data *cs4271plat = codec->dev->platform_data;
    int ret;
 
    //GPIO 핀을 초기화
        if (gpio_request(gpio_nreset, "CS4271 Reset"))
            gpio_nreset = -EINVAL;
 
    // GPIO를 출력(아웃풋)모드로 변경
        gpio_direction_output(gpio_nreset, 0);
 
    // 나노 초만큼 딜레이를 줌
        udelay(1);
 
    // GPIO 핀을 1로 올려 줌 
        gpio_set_value(gpio_nreset, 1);
        /* Give the codec time to wake up */
        udelay(1);
    }
 
    cs4271->gpio_nreset = gpio_nreset;
}
 
 
GPIO 입력 모드

아래는 GPIO를 입력 모드로 설정해서 사용하고 싶을 때의 예시 코드입니다. 특정 상황에서 GPIO PIN의 값을 읽고 싶을 경우 GPIO를 입력 모드로 쓰면 됩니다.
#define GPIO_NUMBER 11 
int gpio_nreset = GPIO_NUMBER;
static int cs4271_probe(struct snd_soc_codec *codec)
{
    struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec);
    struct cs4271_platform_data *cs4271plat = codec->dev->platform_data;
    int ret;
    int gpio_nreset = -EINVAL;

//GPIO 핀을 초기화
        if (gpio_request(gpio_nreset, "CS4271 Reset"))
            gpio_nreset = -EINVAL;
    if (gpio_nreset >= 0) {
        /* Reset codec */
    
// GPIO를 입력(input)모드로 변경
        gpio_direction_input(gpio_nreset);

// 나노 초만큼 딜레이를 줌
        udelay(1);

// GPIO 핀을 읽음 
        if(gpio_get_value(gpio_nreset) == 1) 
              printk(" Oh! GPIO PIN is one");
        /* Give the codec time to wake up */
        udelay(1);
    }
 
이번에 다른 예를 들어볼까요? 디바이스 드라이버 요구 사항을 먼저 알아볼까요?
 
     "만약에 리눅스 드라이버에서 커널 패닉이 발생한 경우 모뎀 프로세서에 GPIO로 이 사실을 알려주고
     (그래야 모뎀도 동작을 멈출 수 있겠죠.) 싶을 때는 GPIO를 어떻게 사용하면 될까?"
 
위 예시에서 언급한 모뎀은 AP 하드웨어와 떨어진 하드웨어를 의미합니다.
 
당연히 출력(output)모드로 사용하면 됩니다. 샘플 코드는 아래와 같아요.
(물론 위 시나리오에서 GPIO로 통신할 수 있는 구조로 회로가 설계되어 있다고 가정 하겠습니다.)

아래는 커널 패닉이 발생되었을 시에 모뎀 프로세서에 이를 알리는 부분인데, 어디선가 이 함수를 호출한다고 가정해요.
static int send_kernel_panic_msg_to_modem_processor(void)
{
    gpio_set_value(gpio_kernel_panic, 1);
}
 
아래는 GPIO를 초기화하는 코드에요.
#define GPIO_NUMBER_FOR_KERNEL_PANIC 12
int gpio_kernel_panic = GPIO_NUMBER_FOR_KERNEL_PANIC;
static int deliver_kernel_panic_accident_to_modem_probe(struct snd_soc_codec *codec)
{

    //GPIO 핀을 초기화
        if (gpio_request(gpio_kernel_panic, "Kernel Panic"))
            gpio_nreset = -EINVAL;

    // GPIO를 출력(아웃풋)모드로 변경하고 기본(default)로 0으로 설정, 
    // 그래야 나중에 1로 이 값을 올려야 정상동작이 가능함
        gpio_direction_output(gpio_kernel_panic, 0);
...
}
 
출력 모드/입력 모드 모두 사용하는 시나리오

두가지 GPIO모드를 사용하는 다른 시나리오를 만들어 볼까요? 유저 스페이스에서 debug fs로 어떤 값이 드라이버에 전달될 때, LCD를 제어하는 시나리오입니다.

     "/d/lcd_driver/val 노드에 1을 써주면 GPIO에 그대로 1이란 값을 써주자."
/d/lcd_driver/val 노드를 유저 스페이스에서 읽으려고 하면 GPIO 상태를 읽어 줘야하는 상황이에요.
#define GPIO_LCD_DRIVER_GET 15
#define GPIO_LCD_DRIVER_SET 16

int gpio_lcd_get = GPIO_LCD_DRIVER_GET;
int gpio_lcd_set = GPIO_LCD_DRIVER_SET; 

// /d/lcd_driver/val 노드를 유저 스페이스에서 읽으려고 하면 lcd_driver_get 함수가 호출되요.
// cat /d/lcd_driver/val
static int lcd_driver_get(void *data, u64 *val)
{
    int ret = 0;
    
    ret = gpio_get_value(gpio_lcd_get);    
    return ret;
}

// /d/lcd_driver/val 노드에 유저 스페이스에서 1로 설정을 하면 lcd_driver_set 함수가 호출이 됨
// echo /d/lcd_driver/val 1
static int lcd_driver_set(void *data, u64 val)
{
    if(val == 1) {
        gpio_set_value(gpio_lcd_set, 1);
    }
}
DEFINE_SIMPLE_ATTRIBUTE(lcd_driver_fops, lcd_driver_get, lcd_driver_set, "%llun");

static int lcd_driver_probe(struct platform_device *pdev)
{
    lcd_debug_debugfs_root = debugfs_create_dir("lcd_driver", NULL);
    debugfs_create_file("val", S_IRUGO, kernel_bsp_debug_debugfs_root, NULL, &lcd_driver_fops);

    //GPIO 핀을 초기화
    gpio_request(gpio_lcd_set, "lcd driver set");
    
    // GPIO를 출력(아웃풋)모드로 변경하고 기본(default)로 0으로 설정, 
    //그래야 나중에 1로 이 값을 올려야 정상동작이 가능함
    gpio_direction_output(gpio_lcd_set, 0);

    //GPIO 핀을 초기화
    gpio_request(gpio_lcd_get, "lcd driver set");

    // GPIO 핀을 입력 모드로 초기화    
    gpio_direction_input(gpio_lcd_get);
    return 0;
}
 
이 정도로 정리하면 잊어 먹지는 않을 것 같아요.
 
     "혹시 글을 읽고 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실하게 답변 올려 드리겠습니다!"
 
 
# Reference: For more information on 'Linux Kernel';
 
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 1
 
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 2
 
 
 
 

'Core BSP 분석 > Core 커널 분석' 카테고리의 다른 글

커널패닉 - ext4 파일 시스템  (0) 2023.05.05
바인더가 깨질 때의 콜 스택  (0) 2023.05.05
슈퍼블록 객체 - ext4 파일시스템  (0) 2023.05.05
덴트리 캐시  (0) 2023.05.05
wakelock - 안드로이드  (0) 2023.05.05

파워 관리 관점에서 안드로이드 디바이스는 간단히 두 가지 상태로 분류할 수 있다. 수면(Deep Sleep)과 깨어난(Wake)이다.

Sleep은 최소한의 파워 리소스를 사용하여 디바이스를 사용할 수 있는 상태이며, Wake는 프로세서 및 대부분의 파워 리소스가 사용을 하여 어떤 작업을 하고 있는 상태이다.  그런데, Wakelock은 디바이스별로 Sleep상태에서 깨우거나 Sleep상태로 들어가는 것을 막을 수 있는 기능이다.

PWL(Partial Wakelock)은 흔히 유저 스패이스에서 구동되고 있는 앱에서 일어나는 wakelock을 의미하며 일어나는 상황 역시 데이터 동기화, 영상 재생 중 화면 꺼짐 방지 등으로 다양하다. 앱을 설치할 때 퍼미션을 살펴보면 시스템 도구 항목 아래 절전 모드로 진입하는 것을 방지한다라고 명시된 부분이 wakelock을 뜻한다. 앱개발 시 WAKE_LOCK이란 퍼미션을 요청해야 하며 허락될 경우 필요한 때에 따라 Sleep상태로 들어가는 것을 방지할 수 있다.

가끔 오랜 시간동안 화면이 꺼져 있어도 디바이스가 Wake 상태였던 것을 종종 확인할 수 있다. 화면이 꺼져 있으며 유저가 디바이스를 사용하고 있지 않지만 wakelock을 통해 프로세서가 활성화되어 필요한 작업을 하고 있다는 것이다.  예를 들어, 특정 앱에서 작업을 스케줄 해둘 경우 시간에 맞춰 작업을 진행하고 끝날 때까진 Wake 상태를 유지한다. Sleep상태에선 최소한의 자원을 이용하지만 Wake 상태에선 파워 리소스를 사용해야 한다. 즉, 배터리를 더 소모한다는 것이다.

'Core BSP 분석 > Core 커널 분석' 카테고리의 다른 글

커널패닉 - ext4 파일 시스템  (0) 2023.05.05
바인더가 깨질 때의 콜 스택  (0) 2023.05.05
슈퍼블록 객체 - ext4 파일시스템  (0) 2023.05.05
덴트리 캐시  (0) 2023.05.05
GPIO - 리눅스 커널  (0) 2023.05.05

가끔 아래와 같이 printk를 추가해서 로그를 보려고 빌드를 하면, 컴파일러가 아래와 같이 에러를 토해내고 컴파일이 중지되는 경우가 있다.
printk("=====!!!!!current task[%s]=======\n", current->comm);

/android/kernel/arch/arm/mach-tegra/timer.c:111:58: error: dereferencing pointer to incomplete type
make[3]: *** [arch/arm/mach-tegra/timer.o] Error 1
make[2]: *** [arch/arm/mach-tegra] Error 2
make[2]: *** Waiting for unfinished jobs....
  CHK     kernel/config_data.h
make[1]: *** [sub-make] Error 2

printk("=====!!!!!current task[%s]=======\n", current->comm);

이 때는 아래와 같은 해더 파일을 추가하면 된다.
#include <linux/sched.h>

printk("[callstack %s,%d] task[%s]========= \n", __func__,__LINE__, current->comm);

한 4년 전 인가요? 아래 코드를 열심히 분석 했었어요. 그런데 나중에 알고 보니 CONFIG_KMEMCHECK, CONFIG_LOCKDEP 컨피그 내 코드가 컴파일 되지 않는 죽은 코드라는 걸 알게 되었어요. 그 때 참 머리를 쥐어 뜯으며 자책했죠. 
static inline void slab_free_hook(struct kmem_cache *s, void *x)
{

 

 kmemleak_free_recursive(x, s->flags);
 
 /*
  * Trouble is that we may no longer disable interrupts in the fast path
  * So in order to make the debug calls that expect irqs to be
  * disabled we need to disable interrupts temporarily.
  */
#if defined(CONFIG_KMEMCHECK) || defined(CONFIG_LOCKDEP)
 {
  unsigned long flags;
 
  local_irq_save(flags);
  kmemcheck_slab_free(s, x, s->object_size);
  debug_check_no_locks_freed(x, s->object_size);
  local_irq_restore(flags);
 }
#endif
 if (!(s->flags & SLAB_DEBUG_OBJECTS))
  debug_check_no_obj_freed(x, s->object_size);
 
 kasan_slab_free(s, x);
 
}
 
이 사실을 어떻게 알아 냈나면, 전처리 파일을 보고 나서죠.
전처리 파일로 slab_free_hook() 함수 구현부를 보았더니 완전 다른 코드로 바뀌어 있었어요.
SLAB_DEBUG_OBJECTS 매크로가 0x00000000UL로 뒤바꾸어 있네요.
static inline __attribute__((always_inline)) __attribute__((no_instrument_function)) void slab_free_hook(struct kmem_cache *s, void *x)
{
 kmemleak_free_recursive(x, s->flags);
# 1314 "/home001/kernel_lover/kernel/mm/slub.c"
 if (!(s->flags & 0x00000000UL))
  debug_check_no_obj_freed(x, s->object_size);
 
 kasan_slab_free(s, x);
}
 
다른 예를 살펴볼까요? get_partial_node() 함수 내 너무나도 유명한 list_for_each_entry_safe API가 보이죠.
static void *get_partial_node(struct kmem_cache *s, struct kmem_cache_node *n,
    struct kmem_cache_cpu *c, gfp_t flags)
{
 struct page *page, *page2;
 void *object = NULL;
 int available = 0;
 int objects;
 
 if (!n || !n->nr_partial)
  return NULL;
 
 spin_lock(&n->list_lock);
 list_for_each_entry_safe(page, page2, &n->partial, lru) {
  void *t;
 
  if (!pfmemalloc_match(page, flags))
   continue;
 
  t = acquire_slab(s, n, page, object == NULL, &objects);
//skip
 }
 spin_unlock(&n->list_lock);
 
list_for_each_entry_safe 함수를 전처리 파일로 보면 어떨까요?
어라, 참 다르죠.
static void *get_partial_node(struct kmem_cache *s, struct kmem_cache_node *n,
    struct kmem_cache_cpu *c, gfp_t flags)
{
 struct page *page, *page2;
 void *object = ((void *)0);
 int available = 0;
 int objects;
 
 
 if (!n || !n->nr_partial)
  return ((void *)0);
 
 spin_lock(&n->list_lock);
 for (page = ({ const typeof( ((typeof(*page) *)0)->lru ) *__mptr = ((&n->partial)->next); (typeof(*page) *)( (char *)__mptr - __builtin_offsetof(typeof(*page), lru) );}), page2 = ({ const typeof( ((typeof(*(page)) *)0)->lru ) *__mptr = ((page)->lru.next); (typeof(*(page)) *)( (char *)__mptr - __builtin_offsetof(typeof(*(page)), lru) );}); &page->lru != (&n->partial); page = page2, page2 = ({ const typeof( ((typeof(*(page2)) *)0)->lru ) *__mptr = ((page2)->lru.next); (typeof(*(page2)) *)( (char *)__mptr - __builtin_offsetof(typeof(*(page2)), lru) );})) {
  void *t;
 
또 다른 예를 살펴볼까요?
static void init_pwq(struct pool_workqueue *pwq, struct workqueue_struct *wq,
       struct worker_pool *pool)
{
 BUG_ON((unsigned long)pwq & WORK_STRUCT_FLAG_MASK);
 
 memset(pwq, 0, sizeof(*pwq));
 
 pwq->pool = pool;
 
역시 전처리 파일 내 함수는 다르게 구현되어 있네요.
static void init_pwq(struct pool_workqueue *pwq, struct workqueue_struct *wq,
       struct worker_pool *pool)
{
 do { if (__builtin_expect(!!((unsigned long)pwq & WORK_STRUCT_FLAG_MASK), 0)) do { asm volatile(".long " "((0xe7f001f2) & 0xFFFFFFFF)" "\n\t" "\n"); __builtin_unreachable(); } while (0); } while (0);
 
 ({ void *__p = (pwq); size_t __n = sizeof(*pwq); if ((__n) != 0) { if (__builtin_constant_p((0)) && (0) == 0) __memzero((__p),(__n)); else memset((__p),(0),(__n)); } (__p); });
 
 pwq->pool = pool;
 
 
전처리 파일을 추출은 하는 방법이 뭐냐구요?
아래 패치를 적용하고 커널 컴파일을 하세요. 그럼 리눅스 커널 오브젝트 파일이 생기는 디렉토리에
.tmp_slub.i 형태로 생성되요.
 
diff --git a/Makefile b/Makefile
index b03ca98..f52240c 100644
--- a/Makefile
+++ b/Makefile
@@ -406,6 +406,7 @@ KBUILD_CFLAGS   := -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs \
      -fno-strict-aliasing -fno-common \
      -Werror-implicit-function-declaration \
      -Wno-format-security \
+     -save-temps=obj \
      -std=gnu89
얼마 전에 아래 패치를 봤어요. 이게 뭐지? 잘 이해가 안 가더라구요.
배열을 초기화하는 것 같기도 한데.
 
diff --git a/drivers/usb/dwc3/debugfs.c b/drivers/usb/dwc3/debugfs.c
index ce7cd96..fb252ec 100644
--- a/drivers/usb/dwc3/debugfs.c
+++ b/drivers/usb/dwc3/debugfs.c
@@ -402,7 +402,7 @@ static ssize_t dwc3_mode_write(struct file *file,
  struct dwc3 *dwc = s->private;
  unsigned long flags;
  u32 mode = 0;
- char buf[32];
+ char buf[32] = {0};
 
  if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
  return -EFAULT;
@@ -482,7 +482,7 @@ static ssize_t dwc3_testmode_write(struct file *file,
  struct dwc3 *dwc = s->private;
  unsigned long flags;
  u32 testmode = 0;
- char buf[32];
+ char buf[32] = {0};
 
  if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
  return -EFAULT;
@@ -589,7 +589,7 @@ static ssize_t dwc3_link_state_write(struct file *file,
  struct dwc3 *dwc = s->private;
  unsigned long flags;
  enum dwc3_link_state state = 0;
- char buf[32];
+ char buf[32] = {0};
 
  if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
  return -EFAULT;
@@ -630,12 +630,10 @@ static ssize_t dwc3_store_ep_num(struct file *file, const char __user *ubuf,
 {
  struct seq_file *s = file->private_data;
  struct dwc3 *dwc = s->private;
- char kbuf[10];
+ char kbuf[10] = {0};
  unsigned int num, dir, temp;
  unsigned long flags;
 
- memset(kbuf, 0, 10);
-
  if (copy_from_user(kbuf, ubuf, count > 10 ? 10 : count))
  return -EFAULT;
 
한 가지 이해가 안 간건. 위 코드에서 왜? memset이란 API를 뺐을까요?
 
커널 패치를 컴파일 한 다음에, 위 패치 반영 전과 후를 비교해보았어요.
아래 ARM instruction은 패치 적용 전 코드인데요, c0629e08 주소에서  __copy_from_user() 함수를
Branch 명령어로 이동하는 Instruction 이외에 특이 코드는 없네요.
static ssize_t dwc3_mode_write(struct file *file,
                const char __user *ubuf, size_t count, loff_t *ppos)
{
c0629d9c:       e1a0c00d        mov     ip, sp
c0629da0:       e92ddbf0             
{r4, r5, r6, r7, r8, r9, fp, ip, lr, pc}
c0629da4:       e24cb004        sub     fp, ip, #4
c0629da8:       e24dd028        sub     sp, sp, #40     ; 0x28
c0629dac:       e52de004        push    {lr}            ; (str lr, [sp, #-4]!)
c0629db0:       ebe7958c        bl      c000f3e8 <__gnu_mcount_nc>
c0629db4:       e1a05002        mov     r5, r2
c0629db8:       e59f2118        ldr     r2, [pc, #280]  ; c0629ed8 <dwc3_mode_write+0x13c>
c0629dbc:       e5923000        ldr     r3, [r2]
c0629dc0:       e1a08002        mov     r8, r2
c0629dc4:       e50b3028        str     r3, [fp, #-40]  ; 0xffffffd8
c0629dc8:       e59030bc        ldr     r3, [r0, #188]  ; 0xbc
c0629dcc:       e1a0000d        mov     r0, sp
c0629dd0:       e3c0cd7f        bic     ip, r0, #8128   ; 0x1fc0
c0629dd4:       e3ccc03f        bic     ip, ip, #63     ; 0x3f
c0629dd8:       e5936060        ldr     r6, [r3, #96]   ; 0x60
c0629ddc:       e355001f        cmp     r5, #31
c0629de0:       31a03005        movcc   r3, r5
c0629de4:       23a0301f        movcs   r3, #31
c0629de8:       e59cc008        ldr     ip, [ip, #8]
c0629dec:       e0910003        adds    r0, r1, r3
c0629df0:       30d0000c        sbcscc  r0, r0, ip
c0629df4:       33a0c000        movcc   ip, #0
c0629df8:       e35c0000        cmp     ip, #0
c0629dfc:       1a000005        bne     c0629e18 <dwc3_mode_write+0x7c>
c0629e00:       e24b0048        sub     r0, fp, #72     ; 0x48
c0629e04:       e1a02003        mov     r2, r3
c0629e08:       ebf2e301        bl      c02e2a14 <__copy_from_user> //<<--
c0629e0c:       e3500000        cmp     r0, #0
 
자자, 이제 패치 적용 후 컴파일을해본 후 어셈블 코드 좀 확인해 보았어요.
근데 ARM GCC 컴파일가 다른 코드를 생성한 것 같아요. 좀 살펴보면,
 
[1]: R1에 0x0을 이동시킴.
[2]: R2에 32를 이동시킴
[3]: R0을 현재 스택 주소에서 0x48 떨어진 곳에 위치시킴(스택 공간을 주는 거죠) 
[4]: 아래 포멧으로 memset 호출
memset(R0: buf, R1: 0x0, R2: 32)
 
static ssize_t dwc3_mode_write(struct file *file,
                 const char __user *ubuf, size_t count, loff_t *ppos)
 {
c0629d9c:       e1a0c00d        mov     ip, sp
c0629da0:       e92ddbf0        push    {r4, r5, r6, r7, r8, r9, fp, ip, lr, pc}
c0629da4:       e24cb004        sub     fp, ip, #4
c0629da8:       e24dd028        sub     sp, sp, #40     ; 0x28
c0629dac:       e52de004        push    {lr}            ; (str lr, [sp, #-4]!)
c0629db0:       ebe7958c        bl      c000f3e8 <__gnu_mcount_nc>
c0629db4:       e59f7130        ldr     r7, [pc, #304]  ; c0629eec <dwc3_mode_write+0x150>
c0629db8:       e1a04001        mov     r4, r1
c0629dbc:       e1a05002        mov     r5, r2
c0629dc0:       e3a01000        mov     r1, #0  //<<--[1]
c0629dc4:       e3a02020        mov     r2, #32  //<<--[2]
c0629dc8:       e5973000        ldr     r3, [r7]
c0629dcc:       e50b3028        str     r3, [fp, #-40]  ; 0xffffffd8
c0629dd0:       e59030bc        ldr     r3, [r0, #188]  ; 0xbc
c0629dd4:       e24b0048        sub     r0, fp, #72     ; 0x48  //<<--[3]
c0629dd8:       e5936060        ldr     r6, [r3, #96]   ; 0x60      
c0629ddc:       ebf2ec6f        bl      c02e4fa0 <memset>        //<<--[4]     
c0629de0:       e1a0200d        mov     r2, sp                       
c0629de4:       e3c21d7f        bic     r1, r2, #8128   ; 0x1fc0
c0629de8:       e3c1103f        bic     r1, r1, #63     ; 0x3f
c0629dec:       e355001f        cmp     r5, #31
c0629df0:       31a03005        movcc   r3, r5
c0629df4:       23a0301f        movcs   r3, #31
c0629df8:       e591c008        ldr     ip, [r1, #8]
c0629dfc:       e0942003        adds    r2, r4, r3
c0629e00:       30d2200c        sbcscc  r2, r2, ip
c0629e04:       33a0c000        movcc   ip, #0
c0629e08:       e35c0000        cmp     ip, #0
c0629e0c:       1a000006        bne     c0629e2c <dwc3_mode_write+0x90>
c0629e10:       e24b0048        sub     r0, fp, #72     ; 0x48
c0629e14:       e1a01004        mov     r1, r4
c0629e18:       e1a02003        mov     r2, r3
c0629e1c:       ebf2e2fc        bl      c02e2a14 <__copy_from_user>
 
 
우아...
char buf[32] = {0}; 와 같이 선언하니 ARM GCC compiler가 친절하게도 memset을 붙혀서
0x0으로 초기화 시켜주네요. char buf[32] = {0}; 구문을 자주 쓰도록 합시다. 그리고 C 코드가 어셈블 코드로 어떻게 변환되는 지도 종종 확인해야 할 것 같아요.
 
 
그리고, 위 패치를 작성하신 분은 이미 이 원리를 알고 있었던 것 같아요.
리눅스 커널에서 제공하는 여러 profile tool 중에 ftrace가 있어요. 매우 강력해요. 
잠깐 기억하는게, 저번 리눅스 커널 세미나에 갔었을 때 리눅스 커널 고수들이 ftrace로 벌이는 향연을 보고 참 라이브 공연에 있는 듯한 착각을 받았어요. ftrace 기능 중 stack tracer 기능을 잠깐 소개하고자 해요.
 
리눅스 커널 고수들 ftrace 로그를 밥 먹듯이 본다고 하네요. 저도 밥 먹듯이 보고는 있지만, 영 실력이 느는 것 같지 않아 짜증이 나지만요. 우선 stack tracer 기능을 좀 소개하려고 해요. 기능도 참 강력해요.
 
아래와 같이 커널 Config를 설정합니다.
--- a/arch/arm/configs/pompeii_com_defconfig
+++ b/arch/arm/configs/pompeii_com_defconfig
@@ -778,6 +777,14 @@ CONFIG_DEBUG_SET_MODULE_RONX=y
 CONFIG_SECURITY_PERF_EVENTS_RESTRICT=y
 CONFIG_SECURITY=y
 CONFIG_SECURITY_NETWORK=y
+CONFIG_DYNAMIC_FTRACE=y
+CONFIG_FUNCTION_TRACER=y
+CONFIG_FUNCTION_GRAPH_TRACER=y
+CONFIG_IRQSOFF_TRACER=y
+CONFIG_SCHED_TRACER=y
+CONFIG_BLK_DEV_IO_TRACER=y
+CONFIG_FUNCTION_PROFILER=y
+CONFIG_STACK_TRACER=y
+CONFIG_TRACER_SNAPSHOT=y
 CONFIG_LSM_MMAP_MIN_ADDR=4096
 CONFIG_HARDENED_USERCOPY=y
 CONFIG_SECURITY_SELINUX=y
 
컴파일 후 아래 available_tracers를 확인하면 끝이죠.
cat /sys/kernel/debug/tracing/available_tracers
 
blk function_graph wakeup_dl wakeup_rt wakeup irqsoff function nop
 
자, 이제 ftrace를 설정해볼까요?
__insert_vmap_area  함수가 어떤 콜스택으로 호출되는지 알고 싶거든요.
 
/d/tracing/buffer_size_kb ftrace 버퍼 사이즈 좀 늘려주시고,
d/tracing/set_ftrace_filter에 __insert_vmap_area 추가하고, 
 d/tracing/options/func_stack_trace을 1로 설정하면 끝이죠.
adb shell "echo  > d/tracing/set_event"
adb shell "sleep 1"
 
adb shell "echo 0 > d/tracing/tracing_on"
adb shell "sleep 1"
 
adb shell " echo 50000 > /d/tracing/buffer_size_kb"
 
adb shell "echo function > d/tracing/current_tracer"
adb shell "sleep 1"
 
adb shell "echo 1 > /d/tracing/events/sched/sched_switch/enable"
adb shell "sleep 1"
 
adb shell "echo __insert_vmap_area  > d/tracing/set_ftrace_filter"
adb shell "sleep 1"
 
adb shell "echo 1 > d/tracing/options/func_stack_trace"
adb shell "sleep 1"
 
adb shell "echo 1 > d/tracing/tracing_on"
adb shell "sleep 1"
 
여기서 중요한 건 ftrace를 설정할 때 잠깐 d/tracing/tracing_on을 0으로 
세팅하여 주시고, 각각 ftrace 설정 시 sleep을 줄 필요가 있어요.
 
왜냐면, ftrace 옵션을 다 설정하고 ftrace가 돌면 시스템 과부하로 시스템이 와치독 리셋으로 죽어버리는 수가 있거든요.
아래 리눅스 커널 커뮤니티에서도 제가 업데이트한 로그가 있는데요. 
 
위 메일링 리스트에서 논의한 내용은, (기억이 가물 가물 하네요) 
ftrace를 graph_tracer로 설정하면 엄청난 ftrace 로그가 찍히는데, Daniel Lezcano이 분은 CPU clock을 높혔더니 stall현상이 안 나왔다는 군요.
 
아무튼 다른 개발자들도 위 설정으로 ftrace을 키면 다른 사이드 이팩트는 없다고 동의했어요.(다른 메일이 날라왔죠.)
 
자, 그럼 위와 같이 설정하고, 5분 후, d/tracing/tracing_on을 0으로 다시 바꿔서 ftrace를 동작을 잠깐 멈추고, 
"d/tracing/trace" 로그를 보면 아래 정보를 볼 수 있어요.
            adbd-5414  [000] ...2   246.224086: __insert_vmap_area <-alloc_vmap_area
            adbd-5414  [000] ...2   246.224118: <stack trace>
 => __insert_vmap_area
 => alloc_vmap_area
 => __get_vm_area_node
 => __vmalloc_node_range
 => __vmalloc_node
 => vmalloc
 => n_tty_open
 => tty_ldisc_open
 => tty_ldisc_hangup
 => __tty_hangup
 => tty_vhangup
 => pty_close
 => tty_release
 => __fput
 => ____fput
 => task_work_run
 => do_work_pending
 => work_pending
// ...생략...
 
            adbd-5414  [000] ...2   247.567047: __insert_vmap_area <-alloc_vmap_area
            adbd-5414  [000] ...2   247.567071: <stack trace>
 => __insert_vmap_area
 => alloc_vmap_area
 => __get_vm_area_node
 => __vmalloc_node_range
 => __vmalloc_node
 => vmalloc
 => n_tty_open
 => tty_ldisc_open
 => tty_ldisc_hangup
 => __tty_hangup
 => tty_vhangup
 => pty_close
 => tty_release
 => __fput
 => ____fput
 => task_work_run
 => do_work_pending
 => work_pending
// ... 생략...
           <...>-5568  [002] ...2   247.572389: __insert_vmap_area <-alloc_vmap_area
           <...>-5568  [002] ...2   247.572407: <stack trace>
 => __insert_vmap_area
 => alloc_vmap_area
 => __get_vm_area_node
 => get_vm_area
 => binder_alloc_mmap_handler
 => binder_mmap
 => mmap_region
 => do_mmap_pgoff
 => vm_mmap_pgoff
 => SyS_mmap_pgoff
 => ret_fast_syscall
 
* 유튜브 강의 동영상도 있으니 같이 들으시면 더 많은 걸 배울 수 있습니다. 



 
 
 
 
#Reference 시스템 콜
 
 
Reference(워크큐)
워크큐(Workqueue) Overview
 
전 리눅스 커널 및 드라이버 코드를 주로 보는데요. 그런데 리눅스 시스템 프로그램 코드가 자주 봐야 해요.
 
아 그리고 userspace에서 tombstone(무덤)이 떨어지면서 크래시가 종종 발생하거든요. 이런 이슈도 잡아야 되요.
에러 시그니처는 아래와 같아요. 흠...
 
Revision: '0'
ABI: 'arm'
pid: 1558, tid: 1891, name: RenderThread >>> com.google.launcher2 <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
r0 00000000 r1 80808080 r2 00000000 r3 00000000
r4 9a979f0c r5 9e7e474c r6 00000000 r7 00000000
backtrace:
#00 pc 00016198 /system/lib/libc.so (strlen+33)
#01 pc 0025b531 /system/app/WebViewGoogle/lib/arm/libwebviewchromium.so
#02 pc 0025b5cb /system/app/WebViewGoogle/lib/arm/libwebviewchromium.so
#03 pc 0025511f /system/app/WebViewGoogle/lib/arm/libwebviewchromium.so
#04 pc 00240fb7 /system/app/WebViewGoogle/lib/arm/libwebviewchromium.so
#05 pc 00214d67 /system/app/WebViewGoogle/lib/arm/libwebviewchromium.so
#06 pc 00212f1f /system/app/WebViewGoogle/lib/arm/libwebviewchromium.so
#07 pc 0021305f /system/app/WebViewGoogle/lib/arm/libwebviewchromium.so
 
이럴 때는 보통 HAL(Hardware Adaptation Layer) 혹은 데몬 코드 리뷰를 하면서 문제점을 점검하며 크래시를 잡는데요.
 
그런데 위와 같이 유저 공간에서 크래시가 발생할 때 커널 패닉을 유발시켜서 코어 덤프를 뜨고 싶을 때가 있어요.
어떤 경우 이런 짓을 하고 싶을까요?
 
뭐 mmap으로 특정 하드웨어를 제어하는 시스템 콜 이후 tombstone이 떨어지던가,
아니면 같은 시간대에 커널 로그를 보고 싶은 경우가 가끔 있어요.
 
아주 가끔은 T32 잘 쓰는 자랑질을 하기 위해서, 
T32 장비를 타겟 디바이스에 연결 후 T32로 break point를 걸고 유저 공간까지 콜스택 까지 볼 수도 있죠.
 
자, 일단 커널 Config를 아래와 같이 설정하고요.
 
diff --git a/arch/arm/configs/pompeii_defconfig b/arch/arm/configs/pompeii_defconfig
index ed1d990..4065c00 100644
--- a/arch/arm/configs/pompeii_defconfig
+++ b/arch/arm/configs/pompeii_defconfig
@@ -777,6 +777,7 @@ CONFIG_DEBUG_SET_MODULE_RONX=y
 CONFIG_SECURITY_PERF_EVENTS_RESTRICT=y
 CONFIG_SECURITY=y
 CONFIG_SECURITY_NETWORK=y
+CONFIG_DEBUG_USER=y
 CONFIG_DYNAMIC_FTRACE=y
 
부트로더에서 "user_debug=24"로 커널 커맨드 라인을 전달해요.
 static void target_common_update_static_bootcmd(void)
 {
+    bootcmd_add_pair("user_debug","24");
+
 
그러면 커널 부팅 시에 아래 코드에서 user_debug 변수를 24로 설정하죠.
[kernel/arch/arm/kernel/traps.c]
#ifdef CONFIG_DEBUG_USER
unsigned int user_debug;
 
static int __init user_debug_setup(char *str)
{
get_option(&str, &user_debug);
return 1;
}
__setup("user_debug=", user_debug_setup);
#endif
 
user_debug 변수를 24로 왜 설정하냐구요? 아래 매크로를 모두 OR 연산 해보면 알 수 있죠.
#define UDBG_UNDEFINED (1 << 0)
#define UDBG_SYSCALL (1 << 1)
#define UDBG_BADABORT (1 << 2)
#define UDBG_SEGV (1 << 3)
#define UDBG_BUS (1 << 4)
 
 
이제 마지막으로, 커널이 부팅하고 나면, 아래와 같이 print-fatal-signals 파일을 1로 설정합니다.
echo 1 > /proc/sys/kernel/print-fatal-signals
 
이제 끝...
 
100% 유저 스페이스에서 tombstone이 떨어지는 디바이스에서 위 설정을 적용하고 실행해보았더니, 아래와 같이 바로 커널 패닉으로 떨어지네요.
참 친절하게도 유저 공간의 프로그램 카운터(PC)와 스택 정보를 찍어주네요.
 
<7>[   72.248793 / 05-21 22:27:31.925][2] android.ui: unhandled page fault (11) at 0x00000000, code 0x005
<1>[   72.248810 / 05-21 22:27:31.925][2] pgd = da0fc000
<1>[   72.252580 / 05-21 22:27:31.925][2] [00000000] *pgd=00000000
<6>[   72.258764 / 05-21 22:27:31.935][2] CPU: 2 PID: 999 Comm: android.ui Tainted: G        W      3.18.66-gbbd0bc4-dirty #3
<6>[   72.258781 / 05-21 22:27:31.935][2] task: d7e0d840 ti: d7f02000 task.ti: d7f02000
<6>[   72.258793 / 05-21 22:27:31.935][2] PC is at 0x73a7c9c0
<6>[   72.258803 / 05-21 22:27:31.935][2] LR is at 0x0
<6>[   72.258814 / 05-21 22:27:31.935][2] pc : [<73a7c9c0>]    lr : [<00000000>]    psr: 00070030
<6>[   72.258814 / 05-21 22:27:31.935][2] sp : 885bc480  ip : 00000000  fp : 131e3218
<6>[   72.258830 / 05-21 22:27:31.935][2] r10: 71488688  r9 : 92778200  r8 : 00000000
<6>[   72.258840 / 05-21 22:27:31.935][2] r7 : 131e2bf0  r6 : 00000000  r5 : 00000001  r4 : 00263c64
<6>[   72.258851 / 05-21 22:27:31.935][2] r3 : 131e3218  r2 : 71488688  r1 : 00000000  r0 : 71488688
<6>[   72.258863 / 05-21 22:27:31.935][2] Flags: nzcv  IRQs on  FIQs on  Mode USER_32  ISA Thumb  Segment user
<6>[   72.258873 / 05-21 22:27:31.935][2] Control: 10c0383d  Table: 9a0fc06a  DAC: 00000051
<6>[   72.258885 / 05-21 22:27:31.935][2] CPU: 2 PID: 999 Comm: android.ui Tainted: G        W      3.18.66-gbbd0bc4-dirty #3
<6>[   72.258907 / 05-21 22:27:31.935][2] [<c010e400>] (unwind_backtrace) from [<c010b4a8>] (show_stack+0x10/0x14)
<6>[   72.258924 / 05-21 22:27:31.935][2] [<c010b4a8>] (show_stack) from [<c0fd0740>] (dump_stack+0x78/0x98)
<6>[   72.258940 / 05-21 22:27:31.935][2] [<c0fd0740>] (dump_stack) from [<c0115f8c>] (__do_user_fault+0x108/0x19c)
<6>[   72.258957 / 05-21 22:27:31.935][2] [<c0115f8c>] (__do_user_fault) from [<c0fe05b8>] (do_page_fault+0x33c/0x3e8)
<6>[   72.258972 / 05-21 22:27:31.935][2] [<c0fe05b8>] (do_page_fault) from [<c0100404>] (do_DataAbort+0x34/0x184)
<6>[   72.258986 / 05-21 22:27:31.935][2] [<c0100404>] (do_DataAbort) from [<c0fdec3c>] (__dabt_usr+0x3c/0x40)
<6>[   72.258997 / 05-21 22:27:31.935][2] Exception stack(0xd7f03fb0 to 0xd7f03ff8)
<6>[   72.259009 / 05-21 22:27:31.935][2] 3fa0:                                     71488688 00000000 71488688 131e3218
<6>[   72.259023 / 05-21 22:27:31.935][2] 3fc0: 00263c64 00000001 00000000 131e2bf0 00000000 92778200 71488688 131e3218
<6>[   72.259036 / 05-21 22:27:31.935][2] 3fe0: 00000000 885bc480 00000000 73a7c9c0 00070030 ffffffff
 
이럴 때 어느 함수에 T32 브레이크 포인트를 걸어야 할까요?
__do_user_fault() 함수의 CONFIG_DEBUG_USER 안에 걸며 딱이겠죠.
 
static void
__do_user_fault(struct task_struct *tsk, unsigned long addr,
unsigned int fsr, unsigned int sig, int code,
struct pt_regs *regs)
{
struct siginfo si;
 
trace_user_fault(tsk, addr, fsr);
 
#ifdef CONFIG_DEBUG_USER
if (((user_debug & UDBG_SEGV) && (sig == SIGSEGV)) ||  //<<-- 여기 브레이크 포인트
    ((user_debug & UDBG_BUS)  && (sig == SIGBUS))) {
printk(KERN_DEBUG "%s: unhandled page fault (%d) at 0x%08lx, code 0x%03x\n",
       tsk->comm, sig, addr, fsr);
show_pte(tsk->mm, addr);
show_regs(regs);
}
#endif
 
__do_user_fault() 함수를 호출하는 루틴
static int __kprobes
do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
{
// ... 생략 ...
 
if (user_mode(regs))
flags |= FAULT_FLAG_USER;
if (fsr & FSR_WRITE)
flags |= FAULT_FLAG_WRITE;
 
// .. 생략 ...
 
__do_user_fault(tsk, addr, fsr, sig, code, regs);
return 0;
 
 no_context:
__do_kernel_fault(mm, addr, fsr, regs);
return 0;
}
 
가끔 T32 잘 쓴다고 자랑해보세요.
 
# Reference: For more information on 'Linux Kernel';
 
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 1
 
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 2
 
 
 
가끔 타겟 보드에서 특정 CPU에서만 커널 패닉으로 시스템이 리셋되는 경우가 있어요.
이럴 때 테스트할 수 있는 유용한 디버깅 패치를 소개하고자 해요.
 
아래 패치는 부팅 할 때 CPU3을 sys node에서 아예 제거시키거든요. 아예 CPU3가 안 돌게 되는거죠.
diff --git a/kernel/cpu.c b/kernel/cpu.c
index 5b4440d..14cce91 100644
--- a/kernel/cpu.c
+++ b/kernel/cpu.c
@@ -510,7 +510,7 @@ static int _cpu_up(unsigned int cpu, int tasks_frozen)
 
        cpu_hotplug_begin();
 
-       if (cpu_online(cpu) || !cpu_present(cpu)) {
+       if ( (cpu == 3) ||  cpu_online(cpu) || !cpu_present(cpu)) {
                ret = -EINVAL;
                goto out;
        }
 
예전에 CPU3에서만 이상한 동작을 하는 보드에서 위 패치를 적용하고 났더니, 부팅 후 크래시 없이 정상 동작하더라구요.
 
결국 실장된 보드의 ARM CPU 불량으로 정리되었죠. 비슷한 이슈를 만났을 때 한번 써보세요.
.
 
Crash Tool로 커널 오브젝트의 rbtree를 디버깅하는 방법에 대해 간단히 소개하고자 해요.
 
아래와 같은 커널 오브젝트가 있다고 가정해보아요. Sys node의 위치는 /sys/devices/system/cpu/cpu3 이겠죠?
현재 struct kobject->cpu는 "cpu3"을 가르키고 있는데요, 그럼 이 커널 오브젝트의 하위 child directory는 어떻게 검색할 수 있을까요?
  (struct kobject *) (struct kobject *)0xDEF78018 = 0xDEF78018 -> (
    (char *) name = 0xDE211780 -> "cpu3",
    (struct list_head) entry = ((struct list_head *) next = 0xDEF8401C, (struct list_head *) prev = 0x
    (struct kobject *) parent = 0xDEADA288,
    (struct kset *) kset = 0xDEAF6540,
    (struct kobj_type *) ktype = 0xC12E9DF0,
    (struct kernfs_node *) sd = 0xDE213240,
    (struct kref) kref = ((atomic_t) refcount = ((int) counter = 3 = 0x3)),
    (unsigned int:1) state_initialized = 1 = 0x1,
    (unsigned int:1) state_in_sysfs = 1 = 0x1,
    (unsigned int:1) state_add_uevent_sent = 1 = 0x1,
    (unsigned int:1) state_remove_uevent_sent = 0 = 0x0,
    (unsigned int:1) uevent_suppress = 0 = 0x0)
 
커널 오브젝트에서 struct kobject->sd.dir로 child directory에 대한 속성을 확인할 수 있어요. 
struct kobject.sd.dir.children.rb_node 멤버에 rb_node 주소가 있는데요. 이 rb_node에 따라 child directory가 위치해 있어요.  
  (struct kobject *) (struct kobject *)0xDEF78018 = 0xDEF78018 -> (
    (char *) name = 0xDE211780 -> "cpu3",
    (struct list_head) entry = ((struct list_head *) next = 0xDEF8401C, (struct list_head *) prev = 0x
    (struct kobject *) parent = 0xDEADA288,
    (struct kset *) kset = 0xDEAF6540,
    (struct kobj_type *) ktype = 0xC12E9DF0,
    (struct kernfs_node *) sd = 0xDE213240 -> (
      (atomic_t) count = ((int) counter = 12 = 0x0C),
      (atomic_t) active = ((int) counter = 0 = 0x0),
      (struct kernfs_node *) parent = 0xDEADBC00,
      (char *) name = 0xDE211840 -> "cpu3",
      (struct rb_node) rb = ((long unsigned int) __rb_parent_color = 3735926096 = 0xDEADB550, (struct
      (void *) ns = 0x0,
      (unsigned int) hash = 25910027 = 0x018B5B0B,
      (struct kernfs_elem_dir) dir = (
        (long unsigned int) subdirs = 2 = 0x2,
        (struct rb_root) children = (
          (struct rb_node *) rb_node = 0xDE213310),
        (struct kernfs_root *) root = 0xDE85EF00),
      (struct kernfs_elem_symlink) symlink = ((struct kernfs_node *) target_kn = 0x2),
      (struct kernfs_elem_attr) attr = ((struct kernfs_ops *) ops = 0x2, (struct kernfs_open_node *) o
      (void *) priv = 0xDEF78018,
 
Crash tool는 rbtree을 아주 쉽게 파싱할 수 있는 명령어를 제공해요. 
아래 명령어를 입력해볼까요?
crash> tree -t rbtree -o kernfs_node.rb -N 0xDE213310
de213300
de213b40
dc9c20c0
de2133c0
de213cc0
 
output 파일은 child directory의 (struct kernfs_node *) 구조체 인스턴스이거든요.
확인해볼까요?
 
0xde213300: "online"
  (struct kernfs_node *) (struct kernfs_node *)0xde213300 = 0xDE213300 -> (
    (atomic_t) count = ((int) counter = 0x3),
    (atomic_t) active = ((int) counter = 0x0),
    (struct kernfs_node *) parent = 0xDE213240,
    (char *) name = 0xC0F82A60 -> "online",
    (struct rb_node) rb = ((long unsigned int) __rb_parent_color = 0x1, (struct rb_node *) rb_right
    (void *) ns = 0x0,
    (unsigned int) hash = 0x4EC7CDD8,
    (struct kernfs_elem_dir) dir = ((long unsigned int) subdirs = 0xC0BDD680, (struct rb_root) child
    (struct kernfs_elem_symlink) symlink = ((struct kernfs_node *) target_kn = 0xC0BDD680),
    (struct kernfs_elem_attr) attr = ((struct kernfs_ops *) ops = 0xC0BDD680, (struct kernfs_open_no
    (void *) priv = 0xC12E9E04,
    (short unsigned int) flags = 0x0252,
    (umode_t) mode = 0x81A4,
    (unsigned int) ino = 0x14C6,
    (struct kernfs_iattrs *) iattr = 0xD9DEDB00)
 
0xde213b40: "power"
  (struct kernfs_node *) (struct kernfs_node *)0xde213b40 = 0xDE213B40 -> (
    (atomic_t) count = ((int) counter = 0x3),
    (atomic_t) active = ((int) counter = 0x0),
    (struct kernfs_node *) parent = 0xDE213240,
    (char *) name = 0xDE214000 -> "power",
    (struct rb_node) rb = ((long unsigned int) __rb_parent_color = 0xDE213310, (struct rb_node *) rb
    (void *) ns = 0x0,
    (unsigned int) hash = 0x12FD28DB,
    (struct kernfs_elem_dir) dir = ((long unsigned int) subdirs = 0x0, (struct rb_root) children = (
    (struct kernfs_elem_symlink) symlink = ((struct kernfs_node *) target_kn = 0x0),
    (struct kernfs_elem_attr) attr = ((struct kernfs_ops *) ops = 0x0, (struct kernfs_open_node *) o
    (void *) priv = 0xDEF78018,
    (short unsigned int) flags = 0x11,
 
0xdc9c20c0: "cpuidle"
  (struct kernfs_node *) (struct kernfs_node *)0xdc9c20c0 = 0xDC9C20C0 -> (
    (atomic_t) count = ((int) counter = 0x0A),
    (atomic_t) active = ((int) counter = 0x0),
    (struct kernfs_node *) parent = 0xDE213240,
    (char *) name = 0xDC993C00 -> "cpuidle",
    (struct rb_node) rb = ((long unsigned int) __rb_parent_color = 0xDE213B51, (struct rb_node *) rb
    (void *) ns = 0x0,
    (unsigned int) hash = 0x0870225E,
    (struct kernfs_elem_dir) dir = ((long unsigned int) subdirs = 0x6, (struct rb_root) children = (
    (struct kernfs_elem_symlink) symlink = ((struct kernfs_node *) target_kn = 0x6),
    (struct kernfs_elem_attr) attr = ((struct kernfs_ops *) ops = 0x6, (struct kernfs_open_node *) o
    (void *) priv = 0xDC992220,
    (short unsigned int) flags = 0x11,
    (umode_t) mode = 0x41ED,
    (unsigned int) ino = 0x4136,
    (struct kernfs_iattrs *) iattr = 0xD9DE4000)
 
0xde2133c0: "subsystem"
  (struct kernfs_node *) (struct kernfs_node *)0xde2133c0 = 0xDE2133C0 -> (
    (atomic_t) count = ((int) counter = 0x3),
    (atomic_t) active = ((int) counter = 0x0),
    (struct kernfs_node *) parent = 0xDE213240,
    (char *) name = 0xDE211900 -> "subsystem",
    (struct rb_node) rb = ((long unsigned int) __rb_parent_color = 0xDE213B51, (struct rb_node *) rb
    (void *) ns = 0x0,
    (unsigned int) hash = 0x45372B66,
    (struct kernfs_elem_dir) dir = ((long unsigned int) subdirs = 0xDEADBE40, (struct rb_root) child
    (struct kernfs_elem_symlink) symlink = ((struct kernfs_node *) target_kn = 0xDEADBE40),
    (struct kernfs_elem_attr) attr = ((struct kernfs_ops *) ops = 0xDEADBE40, (struct kernfs_open_no
    (void *) priv = 0x0,
    (short unsigned int) flags = 0x14,
    (umode_t) mode = 0xA1FF,
    (unsigned int) ino = 0x14C8,
    (struct kernfs_iattrs *) iattr = 0xD9DED400)
 
0xde213cc0: "uevent"
  (struct kernfs_node *) (struct kernfs_node *)0xde213cc0 = 0xDE213CC0 -> (
    (atomic_t) count = ((int) counter = 0x3),
    (atomic_t) active = ((int) counter = 0x0),
    (struct kernfs_node *) parent = 0xDE213240,
    (char *) name = 0xC0F7485C -> "uevent",
    (struct rb_node) rb = ((long unsigned int) __rb_parent_color = 0xDE213311, (struct rb_node *) rb
    (void *) ns = 0x0,
    (unsigned int) hash = 0x57C6BB9D,
    (struct kernfs_elem_dir) dir = ((long unsigned int) subdirs = 0xC0BDD680, (struct rb_root) child
    (struct kernfs_elem_symlink) symlink = ((struct kernfs_node *) target_kn = 0xC0BDD680),
    (struct kernfs_elem_attr) attr = ((struct kernfs_ops *) ops = 0xC0BDD680, (struct kernfs_open_no
    (void *) priv = 0xC12E9E4C,
    (short unsigned int) flags = 0x0252,
    (umode_t) mode = 0x81A4,
    (unsigned int) ino = 0x14C5,
    (struct kernfs_iattrs *) iattr = 0xD9DED500)
 
정리하면,
cpu3(/sys/devices/system/cpu/cpu3)의 하위 디렉토리가 "online", "power", "cpuidle", "subsystem", "uevent" 임을 알 수 있네요.
 
최근 디바이스 드라이버는 Platform Device모델로 적용되고 있거든요. 그래서 디바이스 노드의 상하 관계를 커널 오브젝트를 써서 표현을 많이 하거든요. 그래서 커널 오브젝트 디버깅을 잘하면 좀 더 유용한 정보를 볼 수 있어요.
Crash Tool로 커널 오브젝트의 rbtree를 디버깅해서 parent node를 확인하는 방법에 대해 간단히 소개하고자 해요.
 
아래와 같은 커널 오브젝트가 있다고 가정해보아요. Sys node의 위치는 /sys/devices/system/cpu/cpu3 이겠죠?
현재 struct kobject->cpu는 "cpu3"을 가르키고 있는데요, 그럼 이 커널 오브젝트의 상위 directory는 어떻게 검색할 수 있을까요?
  (struct kobject *) (struct kobject *)0xDEF78018 = 0xDEF78018 -> (
    (char *) name = 0xDE211780 -> "cpu3",
    (struct list_head) entry = ((struct list_head *) next = 0xDEF8401C, (struct list_head *) prev = 0x
    (struct kobject *) parent = 0xDEADA288,
    (struct kset *) kset = 0xDEAF6540,
    (struct kobj_type *) ktype = 0xC12E9DF0,
    (struct kernfs_node *) sd = 0xDE213240 -> (
      (atomic_t) count = ((int) counter = 12 = 0x0C),
      (atomic_t) active = ((int) counter = 0 = 0x0),
      (struct kernfs_node *) parent = 0xDEADBC00,
      (char *) name = 0xDE211840 -> "cpu3",
      (struct rb_node) rb = (
        (long unsigned int) __rb_parent_color = 3735926096 = 0xDEADB550,
        (struct rb_node *) rb_right = 0xDE215C10,
        (struct rb_node *) rb_left = 0xDE2069D0),
 
역시 crash tool에서 rbtree를 파싱할 수 있는 기능을 제공해요.
struct kobject->sd가 갖고 있는 0xDE213240 인스턴스로 parent directory에 대한 rb_node가 있는 struct kernfs_node->rb에 접근이 가능하거든요.
crash> tree -t rbtree -o kernfs_node.rb -N 0xDE213240
de213230
deadbbf0
de862470
de862cb0
de861ff0
 
위 Output 값은 뭘 의미할까요? 좀 복잡한데요.
아래 struct kernfs_node 구조체를 보면 struct kernfs_elem_attr attr 멤버가 보이죠?
 
위 Output은 struct kernfs_elem_attr가 위치한 주소를 가르키고 있거든요.
struct kernfs_elem_attr->notify_next 오프셋이 0x10이니 0x10만큼 더해줘야 해요.
struct kernfs_elem_attr {
const struct kernfs_ops *ops;
struct kernfs_open_node *open;
loff_t size;
struct kernfs_node *notify_next;
};
(where)
struct kernfs_node {
atomic_t count;
atomic_t active;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
// .. 생략..
union {
struct kernfs_elem_dir dir;
struct kernfs_elem_symlink symlink;
struct kernfs_elem_attr attr;
};
.
뭔 소리인지 모르겠다구요? 커널 오브젝트로 디렉토리를 아래 콜 트레이스로 등록되거든요.
나중에 상세 분석할 시간을 갖도록 할께요. 핵심 함수는 kernfs_create_dir_ns/kernfs_new_node 이에요. 
\\vmlinux\kernfs/dir\__kernfs_new_node+0x60
\\vmlinux\slub\kmem_cache_alloc+0x254
\\vmlinux\kernfs/dir\__kernfs_new_node+0x60
\\vmlinux\kernfs/dir\kernfs_new_node+0x2C
\\vmlinux\kernfs/dir\kernfs_create_dir_ns+0x28
\\vmlinux\sysfs/dir\sysfs_create_dir_ns+0x5C
\\vmlinux\kobject\kobject_add_internal+0xAC
\\vmlinux\kobject\kobject_add+0x50
\\vmlinux\base/core\device_add+0x11C
 
이제 확인할 시간이에요.
(struct kernfs_node *)(0xde213230+0x10) = 0xDE213240 -> (
    count = (counter = 12),
    active = (counter = 0),
    parent = 0xDEADBC00,
    name = 0xDE211840 -> "cpu3",
(struct kernfs_node *)(0xdeadbbf0+0x10) = 0xDEADBC00 -> (
    count = (counter = 23),
    active = (counter = 0),
    parent = 0xDE862480,
    name = 0xDEAD96C0 -> "cpu",
(struct kernfs_node *)(0xde862470+0x10) = 0xDE862480 -> (
    count = (counter = 9),
    active = (counter = 0),
    parent = 0xDE862CC0,
    name = 0xDEAD7480 -> "system",
(struct kernfs_node *)(0xde862cb0+0x10) = 0xDE862CC0 -> (
    count = (counter = 36),
    active = (counter = 0),
    parent = 0xDE862000,
    name = 0xDEAF6600 -> "devices",
 
상위 디렉토리가 이렇게 확인 가능하군요.
/sys/devices/system/cpu/cpu3
IPI (Inter Processor Interrupts)란 용어를 들어본 적이 있나요?
각 CPU간 통신을 하고 싶은 경우가 있거든요. 예를 들면, 다른 CPU를 깨우고 싶거나 다른 CPU가
특정 콜백 함수를 호출해서 원하는 동작을 시키고 싶은 경우죠. 
 
CPU 부하을 점검해서 일을 덜하고 있는 다른 CPU에 일을 시키는 네트워크 디바이스 드라이버나,
CPU Governor에서 다른 CPU Frequency를 높이고 싶을 경우에 주로 실행되는데요.
 
그런데 이 때 특정 콜백 함수가 너무 오래 실행되어 응답이 늦을 경우 시스템 오동작을 유발시킬 수가 있구요.
가끔 불량 ARM 코어에서는 특정 CPU가 응답을 늦게 주는 경우가 있어요.
 
이럴 때 써먹으면 좋은 디버깅 패치가 있어요. 아래 패치를 적용하면 CPU간 IPI 통신을 ftrace로 확인할 수 있어요.
diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c
index d031a85..e31341a 100644
--- a/arch/arm64/kernel/smp.c
+++ b/arch/arm64/kernel/smp.c
@@ -841,7 +841,9 @@ void handle_IPI(int ipinr, struct pt_regs *regs)
 
        case IPI_CALL_FUNC:
                irq_enter();
+               trace_printk("++ IPI_CALL_FUNC ++\n");
                generic_smp_call_function_interrupt();
+               trace_printk("-- IPI_CALL_FUNC --\n");
                irq_exit();
                break;
                
diff --git a/kernel/smp.c b/kernel/smp.c
index b2ec21c..fc822cc 100644
--- a/kernel/smp.c
+++ b/kernel/smp.c
@@ -456,12 +456,13 @@ void smp_call_function_many(const struct cpumask *mask,
                        csd->flags |= CSD_FLAG_SYNCHRONOUS;
                csd->func = func;
                csd->info = info;
+               trace_printk("IPI is queued for target cpu : %d\n", cpu);
                llist_add(&csd->llist, &per_cpu(call_single_queue, cpu));
        }
 
        /* Send a message to all CPUs in the map */
        arch_send_call_function_ipi_mask(cfd->cpumask);
-
+       trace_printk("IPI triggered to %d cpus\n", cpumask_weight(cfd->cpumask));
        if (wait) {
                for_each_cpu(cpu, cfd->cpumask) {
                        struct call_single_data *csd;
 
ftrace log를 받아보면 아래와 같은데요. 해석을 좀 해볼께요.
[1][2]: CPU1과 CPU7에 IPI call을 Trigger해요.
[3][4]: CPU7과 CPU1이 IPI call을 잘 받고 있네요.
[5][6]: IPI call이 제대로 종료되었네요.
 <...>-822   [004] ...2    41.459370: smp_call_function_many: IPI target cpu : 1 //<<--[1]
           <...>-822   [004] ...2    41.459372: smp_call_function_many: IPI target cpu : 7 //<<--[2]
           <...>-822   [004] ...2    41.459374: smp_call_function_many: IPI sent to 2 cpus
        Binder_3-1592  [000] d..3    41.459375: sched_switch: prev_comm=Binder_3 prev_pid=1592 prev_prio=120 prev_state=D ==> next_comm=.lge.smartcover next_pid=2570 next_prio=120 extra_prev_state=m 
 logd.reader.per-834   [007] d.h.    41.459377: handle_IPI: + IPI_CALL_FUNC  //<<--[3]
 Ril Proxy Main -1250  [001] d.h.    41.459378: handle_IPI: + IPI_CALL_FUNC //<<--[4] 
 logd.reader.per-834   [007] d.h.    41.459380: handle_IPI: - IPI_CALL_FUNC  //<<--[5]
 Ril Proxy Main -1250  [001] d.h.    41.459382: handle_IPI: - IPI_CALL_FUNC //<<--[6] 
 
아래 ftrace log는 CPU3에서 CPU0/1에 IPI call을 Trigger하는군요. 
 <...>-617   [003] ...2    19.658070: smp_call_function_many: IPI target cpu : 0
           <...>-617   [003] ...2    19.658073: smp_call_function_many: IPI target cpu : 1
           <...>-617   [003] ...2    19.658075: smp_call_function_many: IPI sent to 2 cpus
           <...>-621   [001] d.h1    19.658079: handle_IPI: ++ IPI_CALL_FUNC ++
           <...>-620   [000] d.h1    19.658080: handle_IPI: ++ IPI_CALL_FUNC ++
         mmcqd/0-270   [002] d..3    19.658080: sched_switch: prev_comm=mmcqd/0 prev_pid=270 prev_prio=120 prev_state=R+ ==> next_comm=kworker/2:1H next_pid=293 next_prio=100 NORMAL/1024 ==> NORMAL/88761
           <...>-620   [000] d.h1    19.658084: handle_IPI: -- IPI_CALL_FUNC --
           <...>-621   [001] d.h1    19.658084: handle_IPI: -- IPI_CALL_FUNC --
 
이런 IPI call는 특정 변수에 큐잉(Queue)한 다음에  큐(Queue) 변수에서 요청된 큐를 처리하는 구조인데요.
그 정체는 call_single_queue이구요, per-cpu 타입이에요.
 
이제 Crash Tool로 변수를 직접 확인해 볼까요?
 
1> CPU5 IPI call
CPU5로 IPI call 큐잉(Queue) 정보를 보면 아래와 같아요. 음, CPU5에 drain_local_pages() 함수를 수행하라는 IPI call을 날렸나 보내요.
crash> p call_single_queue
PER-CPU DATA TYPE:
  struct llist_head call_single_queue;
PER-CPU ADDRESSES:
  [0]: edefc4c0
  [1]: edf074c0
  [2]: edf124c0
  [3]: edf1d4c0
  [4]: edf284c0
  [5]: edf334c0
  [6]: edf3e4c0
  [7]: edf494c0 
crash> struct list_head edf334c0 -px
struct list_head {
  struct list_head *next = 0xedf374a0
  -> {
       next = 0xedf37500,
       prev = 0xc01d63d4 <drain_local_pages>
     }  
crash> struct call_single_data 0xedf374a0
struct call_single_data {
  llist = {
    next = 0xedf37500
  },
  func = 0xc01d63d4 <drain_local_pages>,
  info = 0x0,
  flags = 0x1
}
 
2> CPU6 IPI call
이 경우는 CPU6이 smp_callback 함수를 호출하도록 IPI call을 수행하네요. 가장 많이 호출되는 callback 함수이랍니다.
crash> p call_single_queue
PER-CPU DATA TYPE:
  struct llist_head call_single_queue;
PER-CPU ADDRESSES:
  [0]: edefb4c0
  [1]edf064c0
  [2]: edf114c0
  [3]: edf1c4c0
  [4]: edf274c0
  [5]: edf324c0
  [6]: edf3d4c0
  [7]: edf484c0
crash> struct call_function_data -px 0xEC817EE8
struct call_function_data {
  struct call_single_data *csd = 0xedf364c0
  -> {
       llist = {
         next = 0x0
       },
       func = 0xc08487bc <smp_callback>, //<<--
       info = 0x0,
       flags = 0x1
     }
  cpumask = {{
      bits = {0xc042e910}
    }}
}
.
아래 글에서 stack canary에 대한 내용을 다뤘습니다. 스택을 깨는 지 점검하는 루틴인데요.
 
이번에는 다른 디버깅 패치를 작성해서 어떤 루틴이 스택 오염을 시켰는지 점검해보겠습니다.
 
우선 스택이 깨지는 순서를 살펴보겠습니다.
 
1. 아래 함수가 처음 실행될 때 순서로 스택을 푸쉬합니다.
current_sp-0x1c---  R14 // 0xC06FAE8C 실행 시 스택 주소(스택 푸쉬 후)
current_sp-0x18---  R3
current_sp-0x14---  R4 
current_sp-0x10---  R11
current_sp-0xc----  R12
current_sp-0x8----  LR
current_sp-0x4----  PC
current_sp---------      0xC06FAE80 주소에서의 스택 주소
 
해당 코드는 아래와 같습니다.
NSR:C06FAE80|E1A0C00D  touch_irq_handler:  cpy     r12,r13
NSR:C06FAE84|E92DD818                      push    {r3-r4,r11-r12,r14,pc}
NSR:C06FAE88|E24CB004                      sub     r11,r12,#0x4     ; r11,r12,#4
NSR:C06FAE8C|E52DE004                      push    {r14}
 
2. touch_irq_handler() 함수에서 bald_bsp() 란 함수를 호출했다고 가정할께요.
NSR:C06FDE84|E1A0C00D  bald_bsp:  cpy     r12,r13
NSR:C06FDE88|E92DD818                      push    {r3-r4, r14}
 
bald_bsp()의  0xC06FDE88 코드에서 스택 푸쉬를 하고 나면 아래와 같이 스택 푸쉬를 합니다.
current_sp-0x28----  R3  //<<-- bald_bsp()의  0xC06FDE88 코드에서 스택 푸쉬 후 스택 주소
current_sp-0x24----  R4
current_sp-0x20----  R14 // touch_irq_handler에서 bald_bsp()를 호출했으니 touch_irq_handler 함수 내 주소일 것임
current_sp-0x1c---  R14 // 0xC06FAE8C(touch_irq_handler 내) 실행 시 스택 주소(스택 푸쉬 후)
current_sp-0x18---  R3
current_sp-0x14---  R4 
current_sp-0x10---  R11
current_sp-0xc----  R12
current_sp-0x8----  LR
current_sp-0x4----  PC
 
3. bald_bsp() 함수에서 코드를 누군가 잘못 짜서 string copy를 out-of-bound로 처리하면 아래와 같이 스택이 오염됩니다.
current_sp-0x28----  "A"
current_sp-0x24----  "B"
current_sp-0x20---- "C" // R14 // touch_irq_handler에서 bald_bsp()를 호출했으니 touch_irq_handler 함수 내 주소일 것임
current_sp-0x1c---  "D" // 0xC06FAE8C(touch_irq_handler 내) 실행 시 스택 주소(스택 푸쉬 후)
current_sp-0x18---  "E // "R3가 저장된 주소
current_sp-0x14---  "F" // R4 가 저장된 주소 
current_sp-0x10---  R11
current_sp-0xc----  R12
current_sp-0x8----  LR
current_sp-0x4----  PC
 
이제 패치 코드를 작성해 볼 시간입니다. 아래 패치 코드는 touch_irq_handler 함수가 실행되기 직전 스택 주소를 얻어서 스택 덤프를 출력합니다.
diff --git a/drivers/input/touchscreen/touch_core.c b/drivers/input/touchscreen/touch_core.c
index 9cbf6ad..989c6d8 100644
--- a/drivers/input/touchscreen/touch_core.c
+++ b/drivers/input/touchscreen/touch_core.c
@@ -168,10 +168,24 @@ static void touch_core_initialize(struct touch_core_data *ts)
        touch_report_all_event(ts);
 }
 
+#define GET_LR *((unsigned long*)(current_sp+0x20))
+#define ADDRESS_OFFSET 4 // this is 4 bit architecture
+static unsigned int push_stack_size = 10;  // 0x18 in hexa format
+
+static unsigned long stack_array = 0;
+static unsigned long stack_address_offset = 0;
+
 irqreturn_t touch_irq_handler(int irq, void *dev_id)
 {
+       register unsigned long current_sp asm ("sp");
        struct touch_core_data *ts = (struct touch_core_data *) dev_id;
 
+       printk("==== start dump stack dump ==== \n");
+       for( stack_array = 0; stack_array < push_stack_size; stack_array++) {
+               stack_address_offset = stack_array * ADDRESS_OFFSET;
+               printk("[0x%lx] = 0x%lx \n", current_sp + stack_address_offset, *((unsigned long*)(current_sp + stack_address_offset)) );
+       }
+
        bald_bsp();
 
+       printk("==== start dump stack dump ==== \n");
+       for( stack_array = 0; stack_array < push_stack_size; stack_array++) {
+               stack_address_offset = stack_array * ADDRESS_OFFSET;
+               printk("[0x%lx] = 0x%lx \n", current_sp + stack_address_offset, *((unsigned long*)(current_sp + stack_address_offset)) );
+       }
+
 
위 패치의 핵심 코드는 #define GET_LR *((unsigned long*)(current_sp+0x20))인데, 패치 코드를 반영하고 나서 스택 푸쉬되는 바이트를 0x20로 계산했습니다.
   NSR:C06FAE80|E1A0C00D  touch_irq_handler:  cpy     r12,r13
   NSR:C06FAE84|E92DD818                      push    {r3-r4,r11-r12,r14,pc}
   NSR:C06FAE88|E24CB004                      sub     r11,r12,#0x4     ; r11,r12,#4
   NSR:C06FAE8C|E52DE004                      push    {r14}
 
 위 패치를 빌드해서 커널 로그를 받아보면 아래와 같이 bald_bsp() 함수가 호출된 후 스택 덤프가 깨져 있습니다.
<6>[  154.878931 / 02-06 16:59:35.892][0] ==== start dump stack dump ==== 
<6>[  154.878960 / 02-06 16:59:35.892][0] [0xc5233b58] = 0xc06fae80 
<6>[  154.878976 / 02-06 16:59:35.892][0] [0xc5233b5c] = 0xc4226e80 
<6>[  154.878992 / 02-06 16:59:35.892][0] [0xc5233b60] = 0xe1 
<6>[  154.879007 / 02-06 16:59:35.892][0] [0xc5233b64] = 0x0 
<6>[  154.879022 / 02-06 16:59:35.892][0] [0xc5233b68] = 0xc5233bb4 
<6>[  154.879037 / 02-06 16:59:35.892][0] [0xc5233b6c] = 0xc5233b78 
<6>[  154.879052 / 02-06 16:59:35.892][0] [0xc5233b70] = 0xc0085440 
<6>[  154.879067 / 02-06 16:59:35.892][0] [0xc5233b74] = 0xc06fae8c 
<6>[  154.879083 / 02-06 16:59:35.892][0] [0xc5233b78] = 0x20000193 
<6>[  154.879098 / 02-06 16:59:35.892][0] [0xc5233b7c] = 0xc0f72564 
<6>[  154.895630 / 02-06 16:59:35.912][0] ==== start dump stack dump ==== 
<6>[  154.895660 / 02-06 16:59:35.912][0] [0xc5233e58] = 0xA 
<6>[  154.895676 / 02-06 16:59:35.912][0] [0xc5233e5c] = 0xB 
<6>[  154.895691 / 02-06 16:59:35.912][0] [0xc5233e60] = 0xC 
<6>[  154.895706 / 02-06 16:59:35.912][0] [0xc5233e64] = 0xD 
<6>[  154.895723 / 02-06 16:59:35.912][0] [0xc5233e68] = 0xE 
<6>[  154.895740 / 02-06 16:59:35.912][0] [0xc5233e6c] = 0xF 
<6>[  154.895755 / 02-06 16:59:35.912][0] [0xc5233e70] = 0x10 
<6>[  154.895771 / 02-06 16:59:35.912][0] [0xc5233e74] = 0x11 
<6>[  154.895786 / 02-06 16:59:35.912][0] [0xc5233e78] = 0x12 
<6>[  154.895801 / 02-06 16:59:35.912][0] [0xc5233e7c] = 0x13 
 
이런 방식으로 분석하면 어떤 함수가 스택을 깨는 지 점검할 수 있습니다.
 

안드로이드에서 wake lock이란 기능이 있습니다. 그런데 대부분 wake lock을 어떤 모듈이 잡고 있어서 슬립에 못 들어가는 문제가 생기죠. 이럴 때 프로파일링하면 좋은 디버그 패치를 소개합니다.

diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c
old mode 100644
new mode 100755
index 7dbfe1a..2ca0964
--- a/drivers/base/power/wakeup.c
+++ b/drivers/base/power/wakeup.c
@@ -54,6 +54,7 @@ static DEFINE_SPINLOCK(events_lock);
 static void pm_wakeup_timer_fn(unsigned long data);
 
 static LIST_HEAD(wakeup_sources);
+static LIST_HEAD(wakeup_sources_shadow);
 
 static DECLARE_WAIT_QUEUE_HEAD(wakeup_count_wait_queue);
 
@@ -134,6 +135,17 @@ static void wakeup_source_destroy_cb(struct rcu_head *head)
  wakeup_source_destroy(container_of(head, struct wakeup_source, rcu));
 }
 
+struct wakeup_source_shadow {
+ struct list_head entry;
+ const char *name;
+ struct task_struct *p;
+ struct wakeup_source *ws;
+ ktime_t add_ts;
+ ktime_t rmv_ts;
+ int ws_event; /* 1 for add, 2 for remove */
+}; 
+
+
 /**
  * wakeup_source_add - Add given object to the list of wakeup sources.
  * @ws: Wakeup source object to add to the list.
@@ -141,7 +153,8 @@ static void wakeup_source_destroy_cb(struct rcu_head *head)
 void wakeup_source_add(struct wakeup_source *ws)
 {
  unsigned long flags;
-
+ struct wakeup_source_shadow *ws_sh;
+
  if (WARN_ON(!ws))
  return;
 
@@ -152,6 +165,19 @@ void wakeup_source_add(struct wakeup_source *ws)
 
  spin_lock_irqsave(&events_lock, flags);
  list_add_rcu(&ws->entry, &wakeup_sources);
+ if (!strncmp(ws->name, "wlan", strlen("wlan"))) {
+ pr_err("[%s] add hit : %p, %s\n", __func__, ws, ws->name);
+ ws_sh = kmalloc(sizeof(*ws_sh), GFP_ATOMIC);
+ if (ws_sh) {
+ ws_sh->ws = ws;
+ ws_sh->p = current;
+ ws_sh->name = ws->name;
+ ws_sh->add_ts = ws_sh->rmv_ts = ws->last_time;
+ ws_sh->ws_event = 1;
+ list_add_rcu(&ws_sh->entry, &wakeup_sources_shadow);
+ dump_stack();
+ }
+ }
  spin_unlock_irqrestore(&events_lock, flags);
 }
 EXPORT_SYMBOL_GPL(wakeup_source_add);
@@ -163,12 +189,25 @@ EXPORT_SYMBOL_GPL(wakeup_source_add);
 void wakeup_source_remove(struct wakeup_source *ws)
 {
  unsigned long flags;
-
+ struct wakeup_source_shadow *ws_sh;
+
  if (WARN_ON(!ws))
  return;
 
  spin_lock_irqsave(&events_lock, flags);
  list_del_rcu(&ws->entry);
+ if (!strncmp(ws->name, "wlan", strlen("wlan"))) {
+ pr_err("[%s] rmv hit : %p, %s\n", __func__, ws, ws->name);
+ list_for_each_entry(ws_sh, &wakeup_sources_shadow, entry) {
+ if (ws_sh->ws == ws) {
+ ws_sh->rmv_ts = ktime_get();
+ ws_sh->ws_event = 2;
+ ws_sh->p = current;
+ dump_stack();
+ break;
+ }
+ }
+ }
  spin_unlock_irqrestore(&events_lock, flags);
  synchronize_rcu();
 }
 
런큐에 CFS 스케쥴러로 큐잉된 상태로 기다리는 프로세스 목록은 어디서 찾을 수 있을까요?
per-cpu 타입의 runqueues.cfs_tasks 멤버를 찾으면 됩니다.
 
다음은 CPU4 runqueue의 예시입니다.
  (struct rq *) [-] (struct rq*)(((void*)&runqueues)+__per_cpu_offset[4]) = 0xFFFFFFC73E0F2900 -> (
    (raw_spinlock_t) [D:0xFFFFFFC73E0F2900] lock = ((arch_spinlock_t) [D:0xFFFFFFC73E0F2900] raw_lock = ((u16) [D:0xFFFF
    (unsigned int) [D:0xFFFFFFC73E0F2918] nr_running = 0x3,
 //...
    (int) [D:0xFFFFFFC73E0F3298] cpu = 0x4,
    (int) [D:0xFFFFFFC73E0F329C] online = 0x1,
    (struct list_head) [D:0xFFFFFFC73E0F32A0] cfs_tasks = (
      (struct list_head *) [D:0xFFFFFFC73E0F32A0] next = 0xFFFFFFC67B0CE9A8 -> (
        (struct list_head *) [D:0xFFFFFFC67B0CE9A8] next = 0xFFFFFFC73E0F32A0,
        (struct list_head *) [D:0xFFFFFFC67B0CE9B0] prev = 0xFFFFFFC73E0F32A0),
      (struct list_head *) [D:0xFFFFFFC73E0F32A8] prev = 0xFFFFFFC67B0CE9A8),
 
runqueues.cfs_tasks.next는 해당 프로세스의 struct task_struct,se.group_node 멤버로 등록돼 있습니다.
따라서 다음 커맨드를 입력하면 프로세스 태스크 디스크립터 주소를 알아낼 수 있습니다.
  (struct task_struct *) container_of(0xFFFFFFC67B0CE9A8struct task_struct,se.group_node) = 0xFFFFFFC67B0CE900 -> (
    (struct thread_info) thread_info = ((long unsigned int) flags = 0x00400000, (mm_segment_t) addr_limit = 0x0000008000000000, (int) pre
    (long int) state = 0x0,
    (void *) stack = 0xFFFFFFC6A295C000,
//...
    (struct cred *) ptracer_cred = 0x0,
    (struct cred *) real_cred = 0xFFFFFFC72D06E180,
    (struct cred *) cred = 0xFFFFFFC72D06E180,
    (char [16]) comm = "visualizer capt",
유저 공간에서 zygote가 강제 종료되면서 부팅을 못하는 상황입니다. 커널 로그로 아래 메시지를 볼 수 있습니다.
아래 로그는 init 프로세스가 zygote에 SIGABRT(6) 시그널을 전달해서 zygote를 종료시키고 있습니다.
[   46.116831 / 01-02 01:20:24.859][0] init: Service 'zygote' (pid 1777) killed by signal 6
[   46.124107 / 01-02 01:20:24.869][1] init: Service 'zygote' (pid 1777) killing any children in process group
 
그럼 이 동작을 할 때 커널 관점으로 어떤 코드가 수행되는지 살펴보겠습니다.
 
zygote는 커널 공간에서 "main" 이란 쓰레드로 수행됩니다. 그래서 아래 코드를 눈여겨 보면 __send_signal()/send_sigqueue 함수에서 전달하는 struct task_struct *t 가 시그널 전달 받는 프로세스입니다. 이 프로세스의 comm이란 멤버가 프로세스 이름을 담고 있습니다.
 
참고로 아래와 같이 ftrace log를 설정하고, 강제로 zygote를 종료하는 테스트를 수행했습니다.
adb shell "echo 1 > /d/tracing/events/sched/sched_process_exit/enable"
adb shell "echo 1 > /d/tracing/events/sched/sched_process_fork/enable"
 
adb shell "echo 1 > /d/tracing/events/signal/enable"
 
kill -6 918, 즉 kill -6 [pid]로 자이고트에 SIGABRT 시그널을 전달하는 것입니다.
root      918   1     1111816 81936 poll_sched b2ae1ec4 S zygote
austin:/ # kill -6 918
 
그럼 다음과 같은 ftrace 로그를 볼 수 있습니다. main이란 프로세스가 sig 6 SIGABT를 맞고 결국 종료됩니다.
<...>-456   [005] d..2  1186.470605: signal_generate: sig=6 errno=0 code=-6 comm=main pid=918 grp=0 res=0
<...>-918   [005] d..2  1186.470786: signal_deliver: sig=6 errno=0 code=-6 sa_handler=0 sa_flags=14000000
<...>-6202  [007] d..2  1186.470881: signal_deliver: sig=9 errno=0 code=0 sa_handler=0 sa_flags=0
<...>-6205  [005] d..2  1186.470895: signal_deliver: sig=9 errno=0 code=0 sa_handler=0 sa_flags=0
<...>-6203  [007] d..2  1186.470919: signal_deliver: sig=9 errno=0 code=0 sa_handler=0 sa_flags=0
<...>-6204  [005] d..2  1186.470969: signal_deliver: sig=9 errno=0 code=0 sa_handler=0 sa_flags=0
<...>-6203  [007] ...1  1186.471086: sched_process_exit: comm=FinalizerDaemon pid=6203 prio=120
<...>-918   [005] ...1  1186.471092: sched_process_exit: comm=main pid=918 prio=120
<...>-6204  [005] ...1  1186.471780: sched_process_exit: comm=FinalizerWatchd pid=6204 prio=120
<...>-6202  [007] ...1  1186.471798: sched_process_exit: comm=ReferenceQueueD pid=6202 prio=120
 
그래서 프로세스 이름이 main일 때 강제 커널 패닉을 유발하는 코드를 작성했습니다.
diff --git a/kernel/signal.c b/kernel/signal.c
index 7408330..3157014 100644
--- a/kernel/signal.c
+++ b/kernel/signal.c
@@ -1031,6 +1031,12 @@ static int __send_signal(int sig, struct siginfo *info, struct task_struct *t,
        assert_spin_locked(&t->sighand->siglock);
 
        result = TRACE_SIGNAL_IGNORED;
+       if (!strcmp(t->comm, "main")) {
+               show_stack(NULL, NULL);
+               printk("[+][Pompeii] proc %s is ignored \n", t->comm);
+               BUG();
+       }
+
        if (!prepare_signal(sig, t,
                        from_ancestor_ns || (info == SEND_SIG_FORCED)))
                goto ret;
@@ -1120,6 +1126,7 @@ out_set:
        sigaddset(&pending->signal, sig);
        complete_signal(sig, t, group);
 ret:
+       printk("[+][Pompeii] proc %s sig: %d \n", t->comm, sig);
        trace_signal_generate(sig, info, t, group, result);
        return ret;
 }
@@ -1568,6 +1575,15 @@ int send_sigqueue(struct sigqueue *q, struct task_struct *t, int group)
        unsigned long flags;
        int ret, result;
 
+
+       if (!strcmp(t->comm, "main")) {
+               show_stack(NULL, NULL);
+               printk("[+][Pompeii] proc %s is ignored \n", t->comm);
+               show_stack(NULL, NULL);
+
+               BUG();
+       }
+
 
위 코드를 반영하고 타겟 디바이스에 다운로드를 했습니다. 역시 예상대로 부팅 후 20초 정도 시간이 지나자 커널 패닉이 발생했습니다.
 
유저 영역 콜스택까지 확인하니 아래 함수 흐름으로 __send_signal 함수가 호출됩니다.
참고로 심볼 정보가 포함된 libc.so 파일은 symbols\system\lib64에 위치한 libc.so로 로딩해야 한다는 점을 잊지 마세요.
-000|do_undefinstr(regs = 0xFFFFFFC001714630)
-001|__send_signal.constprop.27(sig = 1865235760, info = 0xFFFFFFC06F2D3E40, t = 0xFFFFFFC0000AE488, grou
-002|send_signal(inline)
-002|do_send_sig_info(sig = 6, info = 0xFFFFFFC06F2D3E40, p = 0xFFFFFFC06F2C8000, ?)
-003|do_send_specific(tgid = 748, ?, sig = 6, info = 0xFFFFFFC06F2D3E40)
-004|do_tkill(tgid = 748, pid = 748, sig = 6)
-005|SYSC_tgkill(inline)
-005|sys_tgkill(?, ?, ?)
-006|el0_svc_naked(asm)
 -->|exception
-007|tgkill(asm)
-008|pthread_kill(?, sig = 6)
-009|raise(sig = 748)
-010|abort()
-011|android_log_write_string8_len(?, ?, ?)
 
유저 공간에서 어떤 일이 있었는지 확인해보니 아래 흐름으로 abort가 발생했습니다.
abort -> raise -> pthread_kill -> tgkill
 
정리하면, 유저 영역에서 abort가 발생하면 커널 영역에 signal 6를 전달함을 알 수 있죠.
 
그런데 dump state 로그를 확인하면 아래 콜스택을 볼 수 있습니다.
/system/lib64/libc.so (tgkill+8)
/system/lib64/libc.so (pthread_kill+64)
/system/lib64/libc.so (raise+24)
/system/lib64/libc.so (abort+52)
/system/lib64/libart.so art::Runtime::Abort()                                                                          /system/lib64/libart.so art::LogMessage::~LogMessage()                                                                  /system/lib64/libart.so art::Thread::AssertNoPendingException()    
/system/lib64/libart.so art::ClassLinker::FindClass(art::Thread*, char const*, art::Handle<art::mirror::ClassLoader>)    /system/lib64/libart.so art::ClassLinker::FindArrayClass(art::Thread*, art::mirror::Class**)                                  /system/lib64/libart.so art::JNI::NewObjectArray(_JNIEnv*, int, _jclass*, _jobject*)
/system/lib64/libandroid_runtime.so                                                                                              /system/framework/arm64/boot-framework.oat (offset 0x1663000) (android.hardware.location.ContextHubService.nativeInitialize+124)                 
/system/framework/arm64/boot-framework.oat (offset 0x1663000) (android.hardware.location.ContextHubService.<init>+300)                            
/system/framework/oat/arm64/services.odex (offset 0xd1b000)
 
C++ 코드에서 클래스 메쏘드로 구현된 함수들은 심볼들이 이상하게 보입니다.
아래 폴더에 있는 바이너리 유틸리티 프로그램을 쓰면 원래 심볼로 보여줍니다. c++filt 옵션을 기억합시다. 
android/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin/aarch64-linux-androidkernel-nm              ./aarch64-linux-androidkernel-nm libart.so | c++filt > sym_libart.c
 
참고로 dump state 원래 로그는 아래와 같습니다. 알 수 없는 _ZN3art7Runtime5AbortEv 심볼이 보이죠.
01-03 00:37:24.599  1721  1721 F DEBUG   : backtrace:
01-03 00:37:24.599  1721  1721 F DEBUG   :     #00 pc 000000000006b8c8  /system/lib64/libc.so (tgkill+8)
01-03 00:37:24.599  1721  1721 F DEBUG   :     #01 pc 0000000000068d4c  /system/lib64/libc.so (pthread_kill+64)
01-03 00:37:24.599  1721  1721 F DEBUG   :     #02 pc 00000000000242b8  /system/lib64/libc.so (raise+24)
01-03 00:37:24.599  1721  1721 F DEBUG   :     #03 pc 000000000001ccd4  /system/lib64/libc.so (abort+52)
01-03 00:37:24.599  1721  1721 F DEBUG   :     #04 pc 000000000042c5d0  /system/lib64/libart.so (_ZN3art7Runtime5AbortEv+352)
01-03 00:37:24.599  1721  1721 F DEBUG   :     #05 pc 00000000000e4b24  /system/lib64/libart.so (_ZN3art10LogMessageD2Ev+1204)
01-03 00:37:24.599  1721  1721 F DEBUG   :     #06 pc 000000000044eaf4  /system/lib64/libart.so (_ZNK3art6Thread24AssertNoPendingExceptionEv+836)
01-03 00:37:24.599  1721  1721 F DEBUG   :     #07 pc 0000000000123ec8  /system/lib64/libart.so (_ZN3art11ClassLinker9FindClassEPNS_6ThreadEPKcNS_6HandleINS_6mirror11ClassLoaderEEE+68)
01-03 00:37:24.599  1721  1721 F DEBUG   :     #08 pc 000000000011f170  /system/lib64/libart.so (_ZN3art11ClassLinker14FindArrayClassEPNS_6ThreadEPPNS_6mirror5ClassE+744)
01-03 00:37:24.600  1721  1721 F DEBUG   :     #09 pc 0000000000348c3c  /system/lib64/libart.so (_ZN3art3JNI14NewObjectArrayEP7_JNIEnviP7_jclassP8_jobject+652)
01-03 00:37:24.600  1721  1721 F DEBUG   :     #10 pc 0000000000142254  /system/lib64/libandroid_runtime.so
01-03 00:37:24.600  1721  1721 F DEBUG   :     #11 pc 0000000001baa5d0  /system/framework/arm64/boot-framework.oat (offset 0x1663000) (android.hardware.location.ContextHubService.nativeInitialize+124)
01-03 00:37:24.600  1721  1721 F DEBUG   :     #12 pc 0000000001ba9e80  /system/framework/arm64/boot-framework.oat (offset 0x1663000) (android.hardware.location.ContextHubService.<init>+300)
01-03 00:37:24.600  1721  1721 F DEBUG   :     #13 pc 0000000000dc8474  /system/framework/oat/arm64/services.odex (offset 0xd1b000)
 
정리하면 유저 공간에서 Abort가 떨어지면 abort() -> raise() -> pthread_kill() -> tgkill() 흐름으로 sys_tgkill란 시스템 콜이 호출되어 커널 공간에서는 __send_signal 함수가 호출됩니다.
 
위 동작은 모두 Aarch64 아키텍처 환경에서 설명을 드린 건데요. Aarch32 아키텍처는 약간 동작이 다릅니다.
-000|__do_user_fault(tsk = 0x46, addr = 0, fsr = 5, sig = 11, code = 0, regs = 0x0)
-001|__do_kernel_fault(inline)
-001|do_page_fault(addr = 3883189760, fsr = 5, regs = 0xE5BEFFB0)
-002|do_translation_fault(?, fsr = 3854499232, ?)
-003|do_DataAbort(addr = 0, fsr = 5, regs = 0xE5BEFFB0)
-004|__dabt_usr(asm)
 
arm32 비트 아키텍처에선 유저 영역에서 어보트가 떨어지면 __dabt_usr란 익셉션 벡터가 실행되고, (물론 유저 공간에서 수행 중인 레지스터를 스택에 푸시합니다.)
NSR:C0F733E0|E24DD048  __dabt_usr:       sub     r13,r13,#0x48    ; r13,r13,#72
NSR:C0F733E4|E98D1FFE                    stmib   r13,{r1-r12}
NSR:C0F733E8|EE117F10                    mrc     p15,0x0,r7,c1,c0,0x0   ; p15,0,r7,c1,c0,0 (system control)
NSR:C0F733EC|E51F80B4                    ldr     r8,0xC0F73340
NSR:C0F733F0|E8900038                    ldm     r0,{r3-r5}
NSR:C0F733F4|E28D003C                    add     r0,r13,#0x3C     ; r0,r13,#60
NSR:C0F733F8|E3E06000                    mvn     r6,#0x0          ; r6,#0
NSR:C0F733FC|E58D3000                    str     r3,[r13]
NSR:C0F73400|E5988000                    ldr     r8,[r8]
NSR:C0F73404|E8800070                    stm     r0,{r4-r6}
NSR:C0F73408|E9406000                    stmdb   r0,{r13-r14}^
NSR:C0F7340C|E1380007                    teq     r8,r7
NSR:C0F73410|1E018F10                    mcrne   p15,0x0,r8,c1,c0,0x0   ; p15,0,r8,c1,c0,0 (system control)
NSR:C0F73414|E3A0B000                    mov     r11,#0x0         ; r11,#0
NSR:C0F73418|EBC5BDA8                    bl      0xC00E2AC0       ; trace_hardirqs_off
NSR:C0F7341C|E1A0200D                    cpy     r2,r13
NSR:C0F73420|EBC2BC6E                    bl      0xC00225E0       ; v7_early_abort
 
v7_early_abort란 레이블에서 MMU의 fault register를 r1 레지스터로 읽습니다. 
NSR:C00225E0|EE151F10  v7_early_abort:     mrc     p15,0x0,r1,c5,c0,0x0   ; p15,0,r1,c5,c0,0 (data fault status)
NSR:C00225E4|EE160F10                      mrc     p15,0x0,r0,c6,c0,0x0   ; p15,0,r0,c6,c0,0 (data fault address)
NSR:C00225E8|EAFF97CF                      b       0xC000852C       ; do_DataAbort
 
위 코드에서 "mrc     p15,0x0,r1,c5,c0,0x0" 명령어를 잘 기억하세요.
 
결국 MMU fault register에서 읽은 값에 따라  fsr_info[5] 전역변수에 이미 정의된 do_translation_fault 함수를 호출하는군요.
  (static struct fsr_info [32]) fsr_info = (
    [0] = (
      (int (*)()) fn = 0xC001F58C = do_bad,
      (int) sig = 11,
      (int) code = 0,
      (char *) name = 0xC13F1AD6 = kallsyms_token_index+0x4FC6),
    [1] = (
      (int (*)()) fn = 0xC0021628 = do_alignment,
      (int) sig = 7,
      (int) code = 196609,
      (char *) name = 0xC13F1AE7 = kallsyms_token_index+0x4FD7),
    [2] = (
      (int (*)()) fn = 0xC00184B4 = hw_breakpoint_pending,
      (int) sig = 5,
      (int) code = 196612,
      (char *) name = 0xC13F0451 = kallsyms_token_index+0x3941),
    [3] = (
      (int (*)()) fn = 0xC001F58C = do_bad,
      (int) sig = 11,
      (int) code = 196609,
      (char *) name = 0xC13F17F3 = kallsyms_token_index+0x4CE3),
    [4] = (
      (int (*)()) fn = 0xC0F7528C = do_translation_fault,
      (int) sig = 11,
      (int) code = 196609,
      (char *) name = 0xC13F17D9 = kallsyms_token_index+0x4CC9),
    [5] = (
      (int (*)()) fn = 0xC0F7528C = do_translation_fault,
      (int) sig = 11,
      (int) code = 196609,
      (char *) name = 0xC13F193F = kallsyms_token_index+0x4E2F), 
 
    [6] = ((int (*)()) fn = 0xC001F58C = do_bad, (int) sig = 11, (int) code = 196609, (char *) name = 0xC13F17F3 = kallsyms_token_index+0x4CE3),
    [7] = ((int (*)()) fn = 0xC0F74E4C = do_page_fault, (int) sig = 11, (int) code = 196609, (char *) name = 0xC13F1970 = kallsyms_token_index+0x4E60),
 
# Reference: For more information on 'Linux Kernel';
 
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 1
 
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 2
 
 
 
Overview
 
많은 개발자들은 '브링업'이란 개발 업무를 맡고 있습니다. 이 과정에서 다음과 같은 어려움을 많이 겪습니다.
 
   * 보드 브링업을 하다 보니 컴퓨터의 시작 화면까지 보이지 않는다.
   * 부팅 과정에서 출력하는 화면이 계속 출력된다.
 
이런 이슈를 겪은 적이 있나요? 보통 이런 상황을 겪으면 굉장히 답답하고 짜증이 납니다. 그 이유는 무엇일까요?
 
   * 뭔가 문제가 생겨서 부팅이 안되는 상황인데 그 원인을 파악하기 어렵기 때문입니다. 
 
달리 말씀을 드리면 정확히 어디에 문제가 생겨서 부팅이 안되는지 알 수 없기 때문입니다. 이번 포스팅에서는 이런 상황에서 사용하면 아주 유용한 커널 패치를 소개합니다.
 
아이디어 
 
안드로이드나 라즈비안 타겟 부팅 과정에서 init이나 systemd 프로세스가 *.rc나 *.sh 셸 스크립트를 실행하면서 각종 서비스나 프로세스를 초기화합니다. 하지만 리눅스 커널 입장에서는 위 레이어에 안드로이드나 라즈비안 혹은 우분투가 돌아가는지 모릅니다. 이를 다음과 같이 요약해 볼까요?
 
    "유저 공간에서 프로세스나 스레드 생성 요청이 오면 이를 생성한다."
    "'실행 파일'을 실행할 때 커널은 말 그대로 실행시켜준다."
 
만약 안드로이드에서 각종 서비스를 실행할 때 커널은 이를 어떻게 처리할까요?
 
   * /system/bin/sh 파일 처럼 실행할 뿐입니다. 
 
커널 패치
 
서론이 길었는데, 먼저 커널 패치 코드를 소개합니다.
 
diff --git a/fs/exec.c b/fs/exec.c
index 77c03ce..49b51ce 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1681,6 +1681,37 @@ int search_binary_handler(struct linux_binprm *bprm)
 }
 EXPORT_SYMBOL(search_binary_handler);
 
+#define PROCESS_ARG_BUFF_SIZE 128
+
+void get_process_exec_profile(const char *fname)
+{
+       char process_arg_buf[PROCESS_ARG_BUFF_SIZE] = {0,};
+       int len = 0;
+
+       len = get_cmdline(current, process_arg_buf, PROCESS_ARG_BUFF_SIZE -1);
+
+    if(len == 0) {
+               pr_info("[-] Error: len: %d \n", len);
+               return;
+       }
+
+       if(!fname) {
+               pr_info("[-] Error: fname null \n");
+               return;
+       }
+
+       if (len > PROCESS_ARG_BUFF_SIZE)
+               len = PROCESS_ARG_BUFF_SIZE;
+
+       process_arg_buf[len] = '\0';
+
+       pr_info("[exec:%s-%d] exec_file: %s, arg: [%s] \n",
+                                       current->comm, current->pid, fname, process_arg_buf);
+}
+#endif
+
+
 static int exec_binprm(struct linux_binprm *bprm)
 {
        pid_t old_pid, old_vpid;
@@ -1698,6 +1729,9 @@ static int exec_binprm(struct linux_binprm *bprm)
                trace_sched_process_exec(current, old_pid, bprm);
                ptrace_event(PTRACE_EVENT_EXEC, old_vpid);
                proc_exec_connector(current);
+               get_process_exec_profile(bprm->filename);
        }
 
        return ret;
 
패치 코드의 내용은 실행 파일과 실행 파일에 적용되는 아규먼트 옵션을 커널 로그로 출력해주는 것입니다.
 
커널 로그 확인
 
커밋을 반영하고 커널 로그를 받아보면 아래와 같은 시그니처를 볼 수 있습니다. 참고로 아래 로그는 안드로이드 디바이스에서 받았습니다.
 
2735:[   77.839144 / 01-01 06:09:53.099][6] [exec:init-1] exec_file: /init, arg: [/init]
2748:[   78.472964 / 01-01 06:09:53.739][7] [exec:init-1] exec_file: /system/bin/init, arg: [/system/bin/init]
2761:[   78.957614 / 01-01 06:09:54.229][7] [exec:init-1] exec_file: /system/bin/init, arg: [/system/bin/init]
2776:[   79.170175 / 01-01 06:09:54.429][1] [exec:init-542] exec_file: /system/bin/init, arg: [/system/bin/init]
2777:[   79.172863 / 01-01 06:09:54.439][3] [exec:init-543] exec_file: /system/bin/init, arg: [/system/bin/init]
2780:[   79.298571 / 01-01 06:09:54.559][1] [exec:ueventd-544] exec_file: /system/bin/ueventd, arg: [/system/bin/ueventd]
2781:[   79.317985 / 01-01 06:09:54.579][0] [exec:apexd-545] exec_file: /system/bin/apexd, arg: [/system/bin/apexd]
2804:[   79.584330 / 01-01 06:09:54.849][1] [exec:modprobe-546] exec_file: /vendor/bin/modprobe, arg: [/vendor/bin/modprobe]
2828:[   80.124861 / 01-01 06:09:55.389][2] [exec:modprobe-555] exec_file: /vendor/bin/modprobe, arg: [/vendor/bin/modprobe]
3518:[  385.662491 / 01-01 06:15:00.929][5] [exec:servicemanager-562] exec_file: /system/bin/servicemanager, arg: [/system/bin/servicemanager]
3519:[  385.675577 / 01-01 06:15:00.939][6] [exec:logd-560] exec_file: /system/bin/logd, arg: [/system/bin/logd]
3520:[  385.675634 / 01-01 06:15:00.939][7] [exec:hwservicemanage-563] exec_file: /system/bin/hwservicemanager, arg: [/system/bin/hwservicemanager]
3522:[  385.711358 / 01-01 06:15:00.979][5] [exec:vndservicemanag-564] exec_file: /vendor/bin/vndservicemanager, arg: [/vendor/bin/vndservicemanager]
3524:[  385.763532 / 01-01 06:15:01.029][6] [exec:android.hardwar-567] exec_file: /vendor/bin/hw/android.hardware.keymaster@4.0-service-qti, arg: [/vendor/bin/hw/android.hardware.keymaster@4.0-service-qti]
3525:[  385.764325 / 01-01 06:15:01.029][4] [exec:android.hardwar-569] exec_file: /vendor/bin/hw/android.hardware.keymaster@4.0-strongbox-service-qti, arg: [/vendor/bin/hw/android.hardware.keymaster@4.0-strongbo
x-service-qti]
3527:[  385.843622 / 01-01 06:15:01.109][6] [exec:vold-574] exec_file: /system/bin/vold, arg: [/system/bin/vold]
3532:[  385.929238 / 01-01 06:15:01.189][4] [exec:e2fsck-589] exec_file: /system/bin/e2fsck, arg: [/system/bin/e2fsck]
3547:[  386.199287 / 01-01 06:15:01.459][6] [exec:vdc-597] exec_file: /system/bin/vdc, arg: [/system/bin/vdc]
3557:[  386.342132 / 01-01 06:15:01.609][6] [exec:recovery-refres-598] exec_file: /system/bin/recovery-refresh, arg: [/system/bin/recovery-refresh]
3559:[  386.364102 / 01-01 06:15:01.629][4] [exec:spdaemon-599] exec_file: /vendor/bin/spdaemon, arg: [/vendor/bin/spdaemon]
3560:[  386.375250 / 01-01 06:15:01.639][4] [exec:sec_nvm-600] exec_file: /vendor/bin/sec_nvm, arg: [/vendor/bin/sec_nvm]
3563:[  386.412097 / 01-01 06:15:01.679][7] [exec:ipacm-diag-601] exec_file: /system/vendor/bin/ipacm-diag, arg: [/system/vendor/bin/ipacm-diag]
3565:[  386.414373 / 01-01 06:15:01.679][5] [exec:android.hardwar-603] exec_file: /vendor/bin/hw/android.hardware.atrace@1.0-service, arg: [/vendor/bin/hw/android.hardware.atrace@1.0-service]
3566:[  386.418336 / 01-01 06:15:01.679][5] [exec:ipacm-602] exec_file: /system/vendor/bin/ipacm, arg: [/system/vendor/bin/ipacm]
3567:[  386.436995 / 01-01 06:15:01.699][1] [exec:android.hardwar-604] exec_file: /vendor/bin/hw/android.hardware.boot@1.0-service, arg: [/vendor/bin/hw/android.hardware.boot@1.0-service]
3568:[  386.443772 / 01-01 06:15:01.709][5] [exec:wait_for_keymas-607] exec_file: /system/bin/wait_for_keymaster, arg: [/system/bin/wait_for_keymaster]
 
그렇다면 위 로그를 어떻게 해석할까요? 한 가지 예를 들겠습니다.
 
[  385.662491 / 01-01 06:15:00.929][5] [exec:servicemanager-562] exec_file: /system/bin/servicemanager, arg: [/system/bin/servicemanager] 
 
서비스 매니저가 실행됐다는 정보입니다. arg는 파일을 실행할 때의 아규먼트입니다.
이번에 다른 로그를 봅시다.
 
[  400.060155 / 01-01 06:15:14.839][4] [exec:init.rpi_device.crash-1144] exec_file: /vendor/bin/init.rpi_device.crashdata.sh, arg: [/vendor/bin/sh]
 
'/vendor/bin/init.rpi_device.crashdata.sh' 셸 스크립트를 init.rpi_device.crash.sh 스크립트가 실행합니다.
 
부팅하고 난 다음에 다음 시그니처도 확인할 수 있습니다. 
[  442.944003 / 07-30 05:19:25.069][3] [exec:crash_dump64-4141] exec_file: /system/bin/crash_dump64, arg: [crash_dump64]
 
유저 공간에서 Tombstone이 떨어졌다는 사실을 알 수 있습니다.
 
다음은 가장 흥미로운 로그입니다.
[  450.178083 / 07-30 05:19:32.299][2] [exec:sh-4946] exec_file: /system/bin/sh, arg: [/system/bin/sh]
[  450.224563 / 07-30 05:19:32.349][7] [exec:dmesg-4946] exec_file: /system/bin/dmesg, arg: [dmesg]
 
위 로그는 다음 명령어를 실행했다는 사실을 알려줍니다.
      adb shell dmesg > kernel_log.c
 
결론
 
이 패치를 적용하면 커널 로그를 통해 임베디드 리눅스의 어떤 배포판(안드로이드, 라즈비안)이던 부팅 과정을 확인할 수 있습니다. 리눅스 시스템 개발자들이여, 비슷한 문제를 겪으면 이 패치을 적용해 조금 더 일찍 퇴근합시다. 
 
패치 설명 동영상



가끔 인터럽트 핸들러의 처리 시간이 조금 더 정확히 보고 싶을 때가 있습니다.
이때, 다음과 같은 패치 코드를 적용하면 인터럽트 핸들러의 실행 시간을 측정할 수 있습니다.
 

 

패치 코드는 다음과 같습니다.
 
diff --git a/drivers/mailbox/bcm2835-mailbox.c b/drivers/mailbox/bcm2835-mailbox.c
index a03aeed..e353beb 100644
--- a/drivers/mailbox/bcm2835-mailbox.c
+++ b/drivers/mailbox/bcm2835-mailbox.c
@@ -72,17 +72,29 @@ static struct bcm2835_mbox *bcm2835_link_mbox(struct mbox_chan *link)
        return container_of(link->mbox, struct bcm2835_mbox, controller);
 }
 
+#define HANDLE_TIME 100
+
 static irqreturn_t bcm2835_mbox_irq(int irq, void *dev_id)
 {
+       u64 diff, start_time, curr_time;
        struct bcm2835_mbox *mbox = dev_id;
        struct device *dev = mbox->controller.dev;
        struct mbox_chan *link = &mbox->controller.chans[0];
 
+       start_time = sched_clock();
        while (!(readl(mbox->regs + MAIL0_STA) & ARM_MS_EMPTY)) {
                u32 msg = readl(mbox->regs + MAIL0_RD);
                dev_dbg(dev, "Reply 0x%08X\n", msg);
                mbox_chan_received_data(link, &msg);
        }
+
+       curr_time = sched_clock();
+       diff = curr_time - start_time;
+
+       if (diff > HANDLE_TIME)
+               trace_printk("interrupt: elapsed: %llu, start: %llu \n",
+                               diff, start_time);
+
        return IRQ_HANDLED;
 }
 
코드 내용은 너무 간단합니다. 나노초 단위의 시간 정보를 반환하는 sched_clock() 함수를 사용해서 
실행 시각을 계산하는 코드입니다. 인터럽트 함수의 실행 시간 정보를 저장하는 diff 지역 변수를 ftrace로출력합니다.
 
참고로 인터럽트 핸들러가 실행될 때는 인터럽트 컨텍스트입니다. 빨리 코드가 실행돼야 하니 printk() 함수를 써서 커널 로그를 출력하는 것은 자제해야 합니다.
 
Austin Kim, 궁금한 점이 있으면 댓글로 질문을 남겨주세요.
 
 
 



이번에는 조금 더 재미있는 패치 코드를 같이 볼까요? 다음은 패치 코드의 내용입니다.
 
diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index cd8b61b..128c998 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -49,7 +49,7 @@
 #include <linux/uaccess.h>
 #include <linux/sched/isolation.h>
 #include <linux/nmi.h>
-
+#include <linux/sched_clock.h>
 #include "workqueue_internal.h"
 
 enum {
@@ -2029,6 +2029,18 @@ static bool manage_workers(struct worker *worker)
        return true;
 }
 
+static int work_timer_debug = 1;
+static unsigned long long max_work_handler_time_stamp = 0;
+
+static void work_handler_elapsed_timer_func(struct timer_list *unused)
+{
+       unsigned long long curr_time = sched_clock();
+
+       pr_err("work_handler max elapsed time : %lld, cur time: %lld\n",
+                       max_work_handler_time_stamp, curr_time);
+}
+static DEFINE_TIMER(work_handler_elapsed_timer, work_handler_elapsed_timer_func);
+
 /**
  * process_one_work - process single work
  * @worker: self
@@ -2052,6 +2064,7 @@ __acquires(&pool->lock)
        bool cpu_intensive = pwq->wq->flags & WQ_CPU_INTENSIVE;
        int work_color;
        struct worker *collision;
+       u64 start_time, end_time, elapse_time;
 #ifdef CONFIG_LOCKDEP
        /*
         * It is permissible to free the struct work_struct from
@@ -2149,6 +2162,12 @@ __acquires(&pool->lock)
         * workqueues), so hiding them isn't a problem.
         */
        lockdep_invariant_state(true);
+
+       if (work_timer_debug) {
+               start_time = sched_clock();
+               mod_timer(&work_handler_elapsed_timer, msecs_to_jiffies(500) + jiffies);
+       }
+
        trace_workqueue_execute_start(work);
        worker->current_func(work);
        /*
@@ -2156,6 +2175,19 @@ __acquires(&pool->lock)
         * point will only record its address.
         */
        trace_workqueue_execute_end(work);
+
+       if (work_timer_debug) {
+               end_time = sched_clock();
+               del_timer_sync(&work_handler_elapsed_timer);
+               elapse_time = end_time - start_time;
+
+               if (elapse_time > max_work_handler_time_stamp) {
+                       max_work_handler_time_stamp = elapse_time;
+                       pr_err("work_handler elapsed time : %lld, handler: %pS\n",
+                                       max_work_handler_time_stamp, worker->current_func);
+               }
+       }
+
        lock_map_release(&lockdep_map);
        lock_map_release(&pwq->wq->lockdep_map);
 
@@ -2235,6 +2267,9 @@ static void set_pf_worker(bool val)
  *
  * Return: 0
  */
 
 
위 패치 코드는 2가지 기능을 지원하는데 세부 동작은 두 가지 시나리오에서 생각해볼 수 있습니다.
 
< 첫 번째 시나리오 >
1. 타이머 설정(500ms)
2.         worker->current_func(work); 함수 실행
3. 'worker->current_func(work)' 코드의 실행 시각이 500ms 이내이면 
   타이머 해제
4. 최대 실행 시각을 저장
 
< 두 번째 시나리오 >
1. 타이머 설정(500ms)
2.         worker->current_func(work); 함수 실행
3. 'worker->current_func(work)' 코드의 실행 시각이 500ms 이상이 걸리면  
   타이머 핸들러 함수가 호출됨
4. work_handler_elapsed_timer_func() 타이머 핸들러에서 에러 정보 출력
   : 관련 자료 구조
   : 프로세스의 콜스택
 
임베디드 리눅스 개발자분들은 이 포스팅에 올린 자료를 참고해서 더 빨리 퇴근하셨으면 좋겠습니다.





크래시 유틸리티는 다양한 메모리 및 파일 시스템 디버깅 기능을 제공합니다.
그 중에 유용한 기능 중 하나를 소개합니다.
 
   * files -p '아이노드 주소'
 
먼저 다음 명령어를 입력해 오픈된 파일에 대한 파일 디스크립터, 아이노드 그리고 덴트리를 확인 합니다.
 
   * files <pid>
 
crash> files 1664
PID: 1664   TASK: dc270000  CPU: 1   COMMAND: "Chrome-proc.anim"
ROOT: /    CWD: /
 FD    FILE     DENTRY    INODE    TYPE  PATH
  0  de4d8200  e1a33ab0  e0ce8dd8  CHR   /dev/null
  1  de4d8200  e1a33ab0  e0ce8dd8  CHR   /dev/null
...
 32  dd0cb000  dd4c0000  e02a6af8  REG   /system/framework/framework-res.apk
 33  dd0cbf00  dd470558  e038daf8  REG   /data/media/0/Pictures/Screenshots/Screenshot_20191217-134455.png
 
이어서 아이노드 주소와 함께 아래 명령어를 입력합니다.
 
crash> files -p e038daf8
 INODE    NRPAGES
e038daf8      489
 
  PAGE    PHYSICAL   MAPPING    INDEX CNT FLAGS
e731eba0  7925d000  e038dbf4       269  1 40010228 uptodate,lru,arch_1,mappedtodisk
e7602300  90418000  e038dbf4       517  1 4001026c referenced,uptodate,lru,active,arch_1,mappedtodisk
e71e4980  6f54c000  e038dbf4       803  6 4021026c referenced,uptodate,lru,active,arch_1,mappedtodisk,r_readahead
e71ffdc0  702ee000  e038dbf4       804  4 4021022c referenced,uptodate,lru,arch_1,mappedtodisk,r_readahead
e7b6f900  bbac8000  e038dbf4       805  2 40210268 uptodate,lru,active,arch_1,mappedtodisk,r_readahead
e721e040  71202000  e038dbf4       806  2 40010268 uptodate,lru,active,arch_1,mappedtodisk
 
 
무려 489개의 페이지가 해당 Screenshot_20191217-134455.png 파일의 아이노드에 액세스하고 있습니다.
 
이번에는 다른 명령어를 입력해 볼까?
 
   * files -d <덴트리 주소>
 
crash> files -d dd470558
 DENTRY    INODE    SUPERBLK  TYPE  PATH
dd470558  e038daf8  e1cad800  REG  /data/media/0/Pictures/Screenshots/Screenshot_20191217-134455.png
 
덴트리에 대한 경로 파일을 확인할 수 있다.
 
해당 파일에 대한 슈퍼 블럭을 확인한 ex4 파일 시스템의 슈퍼 블럭임을 확인할 수 있다.
 
crash> struct super_block e1cad800
struct super_block {
  s_list = {
    next = 0xe1cae000,
    prev = 0xe4bc4000
  },
  s_dev = 265289728,
  s_blocksize_bits = 12 '\f',
  s_blocksize = 4096,
  s_maxbytes = 17592186040320,
  s_type = 0xc1733a2c <ext4_fs_type>,
  s_op = 0xc1017f00 <ext4_sops>,
  dq_op = 0xc1017f98 <ext4_quota_operations>,
  s_qcop = 0xc1017fc0 <ext4_qctl_operations>,
  s_export_op = 0xc1017f74 <ext4_export_ops>,
보통 프로덕션 빌드로 이미지를 생성하려고 할 때 커널에서 ftrace를 아예 꺼버리고 싶을 때가 있다. 
이 때 다음 패치를 적용해보자.
 
diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index 40817e4..2cf0d86 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -273,6 +273,7 @@ unsigned long long ns2usecs(u64 nsec)
  */
 static struct trace_array global_trace = {
        .trace_flags = TRACE_DEFAULT_FLAGS,
+       .buffer_disabled = 1,
 };
 
 LIST_HEAD(ftrace_trace_arrays);
 
* 유튜브 강의 동영상도 있으니 같이 들으시면 더 많은 걸 배울 수 있습니다. 



 
크래시 유틸리티 프로그램을 실행할 때 페이지 디스크립터가 위치한 vmemmap의 범위는
'ffffffbc00000000 - ffffffbdffffffff'와 같습니다.
 
crash64: kernel image: ffffff96ff280000 - ffffff9702265000
crash64:      vmemmap: ffffffbc00000000 - ffffffbdffffffff
 
kmalloc 슬랩 캐시를 확인해보겠습니다.
 
crash64> p kmalloc_caches
kmalloc_caches = $1 =
 {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffc6008fdc80, 0xffffffc6008fdb00, 0xffffffc6008fd980, 0xffffffc6008fd800, 0xffffffc6008fd680, 0xffffffc6008fd500, 0xffffffc6008fd380}
 
0xffffffc6008fdb00 주소에 해당하는 슬랩 캐시의 정체는 다음과 같습니다.
 
crash64> struct kmem_cache.name 0xffffffc6008fdb00
  name = 0xffffff9700f6e049 "kmalloc-256"
 
페이지 디스크립터 구조체를 보면 포인터 형인 slab_cache 필드가 있습니다.
crash64> struct page
struct page {
    unsigned long flags;
...
        struct {
            union {
                struct list_head slab_list;
                struct {
                    struct page *next;
                    int pages;
                    int pobjects;
                };
            };
            struct kmem_cache *slab_cache;
            void *freelist;
...
 
crash64_kaslr> struct page.slab_cache
struct page {
  [0x18]         struct kmem_cache *slab_cache;
}
 
그래서 슬랩 캐시의 주소 검색을 통해 해당 슬랩 캐시의 페이지 갯수를 확인할 수 있습니다.
 
crash64_kaslr> search 0xffffffc6008fdb00
ffffff80156ab8a0: ffffffc6008fdb00
ffffff97012c4a00: ffffffc6008fdb00
ffffffbf18023e98: ffffffc6008fdb00
...
 
vmemmap의 시작 주소가 ffffffbc00000000 이므로 ffffff80156ab8a0와 ffffff97012c4a00 주소에 위치한 슬랩 캐시의 주소는 별 의미가 없습니다.
 
vmemmap 메모리 공간에서 "kmalloc-256" 슬랩 캐시를 포함하는 페이지 디스크립터의 갯수는 880개입니다.
 
crash64> search 0xffffffc6008fdb00 | wc -l
882
 
// 880 = 882 - 2
 
이번에는 "kmalloc-128" 슬랩 캐시의 페이지 디스크립터의 갯수를 확인하겠습니다.
 
crash64_kaslr> struct kmem_cache.name 0xffffffc6008fdb00 
  name = 0xffffff9700f6e03d "kmalloc-128"
 
엄청난 갯수의 페이지 디스크립터를 사용하고 있습니다.
 
crash64> search  0xffffffc6008fdc80 | wc -l
891606
 
vmemmap의 범위('ffffffbc00000000 - ffffffbdffffffff') 밖의 주소가 포함하는 "kmalloc-128" 슬랩 캐시의 갯수는
16이므로, 891590(891606 - 16) 개의 페이지를 소진하고 있습니다.
 
crash64> search  0xffffffc6008fdc80
ffffff80083ab670: ffffffc6008fdc80
ffffff80083ab6f8: ffffffc6008fdc80
ffffff800951bc20: ffffffc6008fdc80
ffffff800951bc30: ffffffc6008fdc80
ffffff800951bc50: ffffffc6008fdc80
ffffff800958b8b0: ffffffc6008fdc80
ffffff800958b8c0: ffffffc6008fdc80
ffffff80145bbba0: ffffffc6008fdc80
ffffff8015a0bab0: ffffffc6008fdc80
ffffff8015e9bb30: ffffffc6008fdc80
ffffff801647b6d0: ffffffc6008fdc80
ffffff801772b830: ffffffc6008fdc80
ffffff8017a5b830: ffffffc6008fdc80
ffffff8017a5b840: ffffffc6008fdc80
ffffff8021223ab0: ffffffc6008fdc80
ffffff97012c49f8: ffffffc6008fdc80
 
"kmalloc-128" 슬랩 오브젝트의 Leak을 확인할 필요가 있습니다.
 
 
# Reference: For more information on 'Linux Kernel';
 
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 1
 
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 2
 
 
 
 
 
 
 
 
 
 
 
 
 
리눅스 프로젝트를 개발하면 코어덤프(coredump)를 열어서 크래시가 발생한 원인을 분석할 때가 많습니다.
많은 개발자 분들이 코어덤프를 열어서 크래시가 발생한 원인을 분석하죠.
 
코어덤프를 열어 gdb를 사용해 디버깅할 때 가장 짜증나는 것 중 하나는 공유 라이브러리를 제대로 로딩하지 못해
콜 스택이 보이지 않을 때 입니다.
 
이번에는 코어덤프를 로딩할 때 필요한 정보 중 하나인 공유 라이브러리의 정보(패스/이름)을 확인하는 방법을 소개합니다.
 
깨진 콜 스택 확인하기
 
먼저 콜 스택을 보겠습니다.
 
(gdb) bt
#0  0x0000007f7def1808 in __glibc_raise (sig=sig@entry=6) at /usr/glibc/raise.c:1354
#1  0x0000007f7def2d80 in __glibc_abort () at /usr/glibc/abort.c:489
#2  0x0000007f7deeadbc in __assert_fail_base()     at /usr/glibc/assert.c:912
#3  0x0000007f7deeae6c in __glibc___assert_fail () at  /usr/glibc/assert.c:1014
#4  0x0000007f7e4f13ec in power_railway_delay () at /usr/src/powerrail/powerrail.c:9489
#5  0x0000007f7efa5af4 in ?? ()
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
 
위 메시지를 보면 #5 줄에서 콜 스택이 깨진 걸 볼 수 있습니다.
 
#5  0x0000007f7efa5af4 in ?? ()
 
0x0000007f7efa5af4 주소가 뭔지 모르겠다는 소리죠.
 
공유 라이브러리 이름 확인하기
 
이번에는 'info share' 명령어를 입력해 공용 라이브러리의 경로를 확인합시다.
 
(gdb) info share
From                To                  Syms Read   Shared Object Library
                                        No          /usr/lib/libPowerRailwayManagement.so
0x0000007f7ee1cf30  0x0000007f7eec9c9c  Yes          /usr/lib/librsi.so.0.1
 
가장 첫 번째 줄을 보면 libPowerRailwayManagement.so 라이브러리가 제대로 로딩이 안 됐다고 확인됩니다.
 
No          /usr/lib/libPowerRailwayManagement.so
 
 
공유 라이브러리 오프셋 확인하기
 
리눅스 터미널을 하나 더 열고, './readelf -n coredump' 명령어를 입력해 코어덤프에 있는 NT_FILE 노트를 확인합시다. 참고로 NT_FILE 노트에는 코어덤프에서 공유 라이브러리 정보가 포함돼 있습니다.
 
baldcandy.kim# ./readelf -n coredump 
 
Displaying notes found at file offset 0x00005168 with length 0x00010c58:
  Owner                 Data size       Description
  CORE                 0x00000188       NT_PRSTATUS (prstatus structure)
  CORE                 0x00000088       NT_PRPSINFO (prpsinfo structure)
  CORE                 0x00000080       NT_SIGINFO (siginfo_t data)
  CORE                 0x00000130       NT_AUXV (auxiliary vector)
  CORE                 0x00002b9d       NT_FILE (mapped files)
    Page size: 4096
                 Start                 End         Page Offset
...
    0x0000007f7ef21000  0x0000007f7f18a000  0x0000000000000000
        /usr/lib/libPowerRailwayManagement.so
    0x0000007f7f18a000  0x0000007f7f19a000  0x0000000000000269
        /usr/lib/libPowerRailwayManagement.so
    0x0000007f7f19a000  0x0000007f7f1a3000  0x0000000000000269
        /usr/lib/libPowerRailwayManagement.so
    0x0000007f7f1a3000  0x0000007f7f1a5000  0x0000000000000272
        /usr/lib/libPowerRailwayManagement.so
 
libPowerRailwayManagement.so 라이브러리의 실행 오프셋은 0x0000007f7ef21000임을 알 수 있습니다.
 
이번에는 libPowerRailwayManagement.so 라이브러리 파일을 찾아서 헤더 정보를 확인합시다.
이를 위해 'readelf -S libPowerRailwayManagement.so' 명령어를 입력해야 합니다.
 
baldcandy.kim# readelf -S libPowerRailwayManagement.so
There are 27 section headers, starting at offset 0x273728:
 
Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .note.gnu.build-i NOTE             00000000000001c8  000001c8
       0000000000000024  0000000000000000   A       0     0     4
...
  [10] .plt              PROGBITS         0000000000074b60  00074b60
       000000000000a110  0000000000000010  AX       0     0     16
  [11] .text             PROGBITS         000000000007ec70  0007ec70
       0000000000160ebc  0000000000000000  AX       0     0     16
 
'[11]' 번째 줄을 보면 .text 섹션 헤더의 오프셋은 0x0007ec70으로 확인됩니다.
 
결국 libPowerRailwayManagement.so 라이브러리의 오프셋은 다음 계산식으로 0x7F7EF9FC70입니다.
 
0x7F7EF9FC70 = 0x0000007f7ef21000 + 0x0007ec70
 
공유 라이브러리 로딩하기
 
다시 GDB 프로그램 화면으로 되돌아와, 다음 명령어를 입력해 라이브러리 패스를 추가합시다.
 
'add-symbol-file ./libPowerRailwayManagement.so 0x7F7EF9FC70'
 
(gdb) add-symbol-file ./libPowerRailwayManagement.so 0x7F7EF9FC70
add symbol table from file "./libPowerRailwayManagement.so" at
        .text_addr = 0x7f7ef9fc70
(y or n) y
 
명령어를 입력하면 '(y or n)' 텍스트가 출력되면서 .text_addr를 0x7f7ef9fc70로 지정하도 되는지 묻습니다.
바로 'y'를 선택합니다. 그러면 다음과 같은 화면이 보일 것입니다.
 
(y or n) y
Reading symbols from ./libPowerRailwayManagement.so...(no debugging symbols found)...done.
 
공유 라이브러리를 로딩한 다음 콜 스택을 확인하니 깨진 콜 스택이 복구해 볼 수 있습니다.
 
(gdb) bt
#0  0x0000007f7def1808 in __glibc_raise (sig=sig@entry=6) at /usr/glibc/raise.c:1354
#1  0x0000007f7def2d80 in __glibc_abort () at /usr/glibc/abort.c:489
#2  0x0000007f7deeadbc in __assert_fail_base()     at /usr/glibc/assert.c:912
#3  0x0000007f7deeae6c in __glibc___assert_fail () at  /usr/glibc/assert.c:1014
#4  0x0000007f7e4f13ec in power_railway_delay () at /usr/src/powerrail/powerrail.c:9489
#5  0x0000007f7efa5af4 in PowerManager::set_powerrail_delay() 
#6  0x0000007f7efaf774 in PowerManager::set_powerrail_run() 
#7  0x0000007f7efb17e8 in PowerManager::powerrail_set_railway() 
#8  0x0000007f7efb3018 in PowerManager::powerrail_init(void*) ()
#9  0x0000007f7efb3c60 in ?? ()
#10 0x0000007f7e26b020 in start_thread (arg=0x147f7e28f010 <__pthread_keys+15752>) at /usr/src/pthread_create.c:11335
#11 0x0000007f7df88970 in thread_start () at ../sysdeps/unix/sysv/linux/aarch64/clone.S:89
 
아래는 공유 라이브러리를 로딩하기 전의 깨진 콜 스택 정보입니다.
 
(gdb) bt
#0  0x0000007f7def1808 in __glibc_raise (sig=sig@entry=6) at /usr/glibc/raise.c:1354
#1  0x0000007f7def2d80 in __glibc_abort () at /usr/glibc/abort.c:489
#2  0x0000007f7deeadbc in __assert_fail_base()     at /usr/glibc/assert.c:912
#3  0x0000007f7deeae6c in __glibc___assert_fail () at  /usr/glibc/assert.c:1014
#4  0x0000007f7e4f13ec in power_railway_delay () at /usr/src/powerrail/powerrail.c:9489
#5  0x0000007f7efa5af4 in ?? ()
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
 
 
---
"이 포스팅이 유익하다고 생각되시면 공감 혹은 댓글로 응원해주시면 감사하겠습니다. 
"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!"
 
​Thanks,
Guillermo Austin Kim(austindh.kim@gmail.com)
---
gdb와 같은 프로그램이 설치된 경로를 확인하고 싶을 때가 있습니다.
이 때 다음 명령어를 사용하면 됩니다.
 
'which -a'
 
아래는 터미널에서 'which -a' 명령어를 사용한 예시입니다.
 
baldcandy:#/etc$ which -a gdb-multiarch
/usr/bin/gdb-multiarch
 
gdb-multiarch가 '/usr/bin/gdb-multiarch' 에 위치해 있군요.
 
이번에는 gdb 프로그램의 위치를 확인해보겠습니다.
 
baldcandy:#/etc$ which -a gdb
/usr/bin/gdb
 
gdb가 '/usr/bin/gdb' 에 위치해 있군요.
 
---
"이 포스팅이 유익하다고 생각되시면 공감 혹은 댓글로 응원해주시면 감사하겠습니다. 
"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!"
 
​Thanks,
Guillermo Austin Kim(austindh.kim@gmail.com)
---
아래 패치도 정말 유용하다.
 
 * 유저 프로세스가 ASSET될 때 콜 스택 출력
 * 부팅 과정에 오픈하는 파일을 출력
 * 실행되는 파일 이름 출력
 
diff --git a/arch/arm/kernel/traps.c b/arch/arm/kernel/traps.c
index 9f5d818..4d6df0c 100644
--- a/arch/arm/kernel/traps.c
+++ b/arch/arm/kernel/traps.c
@@ -37,7 +37,8 @@
 #include <asm/tls.h>
 #include <asm/system_misc.h>
 #include <asm/opcodes.h>
-
+#include <linux/file.h>
+#include <linux/slab.h>
 
 static const char *handler[]= {
  "prefetch abort",
@@ -60,6 +61,48 @@ static int __init user_debug_setup(char *str)
 __setup("user_debug=", user_debug_setup);
 #endif
 
+
+void trace_exe_filename(void)
+{
+ struct file *exe_file;
+ char *pathbuf, *fname;
+
+ printk("[+][trace][%s][%s,%d] caller: %pS  \n",
+ current->comm, __func__,__LINE__,  (void *)__builtin_return_address(0));
+
+ exe_file = get_mm_exe_file(current->mm);
+
+ if (!exe_file)
+ {
+ printk("[-] path unknown: %s \n", current->comm);
+ return;
+ }
+
+ pathbuf = kmalloc(PATH_MAX, GFP_TEMPORARY);
+
+ if (!pathbuf) {
+ printk("[-] memory starved: %s \n", current->comm);
+ goto put_exe_file;
+ }
+
+ fname = d_path(&exe_file->f_path, pathbuf, PATH_MAX);
+
+ if (IS_ERR(fname)) {
+ printk("[-] path error: %s \n", current->comm);
+ goto free_buf;
+ }
+
+ printk("[+][%s]: exe_file: %s \n", current->comm, fname);
+
+free_buf:
+ kfree(pathbuf);
+put_exe_file:
+ fput(exe_file);
+
+ return;
+}
+
+
 static void dump_mem(const char *, const char *, unsigned long, unsigned long);
 
 void dump_backtrace_entry(unsigned long where, unsigned long from, unsigned long frame)
@@ -409,6 +452,8 @@ asmlinkage void __exception do_undefinstr(struct pt_regs *regs)
  siginfo_t info;
  void __user *pc;
 
+ printk("[+][trace][%s][%s,%d]: \n", current->comm, __func__,__LINE__);
+
  pc = (void __user *)instruction_pointer(regs);
 
  if (processor_mode(regs) == SVC_MODE) {
@@ -444,14 +489,11 @@ asmlinkage void __exception do_undefinstr(struct pt_regs *regs)
  return;
 
 die_sig:
-#ifdef CONFIG_DEBUG_USER
- if (user_debug & UDBG_UNDEFINED) {
- printk(KERN_INFO "%s (%d): undefined instruction: pc=%p\n",
+    printk(KERN_INFO "%s (%d): undefined instruction: pc=%p\n",
  current->comm, task_pid_nr(current), pc);
- __show_regs(regs);
- dump_instr(KERN_INFO, regs);
- }
-#endif
+ __show_regs(regs);
+ dump_instr(KERN_INFO, regs);
+ trace_exe_filename();
 
  info.si_signo = SIGILL;
  info.si_errno = 0;
@@ -508,19 +550,18 @@ static int bad_syscall(int n, struct pt_regs *regs)
  struct thread_info *thread = current_thread_info();
  siginfo_t info;
 
+ printk("[+][trace][%s][%s,%d]: \n", current->comm, __func__,__LINE__);
+
  if ((current->personality & PER_MASK) != PER_LINUX &&
      thread->exec_domain->handler) {
  thread->exec_domain->handler(n, regs);
  return regs->ARM_r0;
  }
 
-#ifdef CONFIG_DEBUG_USER
- if (user_debug & UDBG_SYSCALL) {
- printk(KERN_ERR "[%d] %s: obsolete system call %08x.\n",
+ printk(KERN_ERR "[%d] %s: obsolete system call %08x.\n",
  task_pid_nr(current), current->comm, n);
- dump_instr(KERN_ERR, regs);
- }
-#endif
+ dump_instr(KERN_ERR, regs);
+ trace_exe_filename();
 
  info.si_signo = SIGILL;
  info.si_errno = 0;
@@ -688,21 +729,15 @@ asmlinkage int arm_syscall(int no, struct pt_regs *regs)
  return -ENOSYS;
  break;
  }
-#ifdef CONFIG_DEBUG_USER
- /*
-  * experience shows that these seem to indicate that
-  * something catastrophic has happened
-  */
- if (user_debug & UDBG_SYSCALL) {
- printk("[%d] %s: arm syscall %d\n",
-        task_pid_nr(current), current->comm, no);
- dump_instr("", regs);
- if (user_mode(regs)) {
- __show_regs(regs);
- c_backtrace(frame_pointer(regs), processor_mode(regs));
- }
- }
-#endif
+
+  printk("[%d] %s: arm syscall %d\n",
+  task_pid_nr(current), current->comm, no);
+  dump_instr("", regs);
+  __show_regs(regs);
+  c_backtrace(frame_pointer(regs), processor_mode(regs));
+
+ trace_exe_filename();
+
  info.si_signo = SIGILL;
  info.si_errno = 0;
  info.si_code  = ILL_ILLTRP;
@@ -769,14 +804,11 @@ baddataabort(int code, unsigned long instr, struct pt_regs *regs)
  unsigned long addr = instruction_pointer(regs);
  siginfo_t info;
 
-#ifdef CONFIG_DEBUG_USER
- if (user_debug & UDBG_BADABORT) {
- printk(KERN_ERR "[%d] %s: bad data abort: code %d instr 0x%08lx\n",
+ printk(KERN_ERR "[%d] %s: bad data abort: code %d instr 0x%08lx\n",
  task_pid_nr(current), current->comm, code, instr);
- dump_instr(KERN_ERR, regs);
- show_pte(current->mm, addr);
- }
-#endif
+ dump_instr(KERN_ERR, regs);
+ show_pte(current->mm, addr);
+ trace_exe_filename();
 
  info.si_signo = SIGILL;
  info.si_errno = 0;
diff --git a/fs/exec.c b/fs/exec.c
index b7a5f46..b6089dc 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1425,6 +1425,34 @@ int search_binary_handler(struct linux_binprm *bprm)
 }
 EXPORT_SYMBOL(search_binary_handler);
 
+#define PROCESS_ARG_BUFF_SIZE 128
+
+void trace_process_exec_profile(const char *fname)
+{
+       char process_arg_buf[PROCESS_ARG_BUFF_SIZE] = {0,};
+       int len = 0;
+
+       len = get_cmdline(current, process_arg_buf, PROCESS_ARG_BUFF_SIZE -1);
+
+       if(len == 0) {
+               pr_info("[-] Error: len: %d \n", len);
+               return;
+       }
+
+       if(!fname) {
+               pr_info("[-] Error: fname null \n");
+               return;
+       }
+
+       if (len > PROCESS_ARG_BUFF_SIZE)
+               len = PROCESS_ARG_BUFF_SIZE;
+
+       process_arg_buf[len] = '\0';
+
+       pr_info("[exec:%s-%d] exec_file: %s, arg: [%s] \n",
+                                       current->comm, current->pid, fname, process_arg_buf);
+}
+
 static int exec_binprm(struct linux_binprm *bprm)
 {
  pid_t old_pid, old_vpid;
@@ -1442,6 +1470,8 @@ static int exec_binprm(struct linux_binprm *bprm)
  trace_sched_process_exec(current, old_pid, bprm);
  ptrace_event(PTRACE_EVENT_EXEC, old_vpid);
  proc_exec_connector(current);
+
+ trace_process_exec_profile(bprm->filename);
  }
 
  return ret;
diff --git a/arch/arm/kernel/traps.c b/arch/arm/kernel/traps.c
index f702f2b..299427d 100644
--- a/arch/arm/kernel/traps.c
+++ b/arch/arm/kernel/traps.c

 

@@ -435,6 +435,45 @@ int call_undef_hook(struct pt_regs *regs, unsigned int instr)
  return fn ? fn(regs, instr) : 1;
 }
 
+void trace_exe_filename(void)
+{
+ struct file *exe_file;
+ char *pathbuf, *fname;
+
+ printk("[+][trace][%s][%s,%d] caller: %pS  \n",
+ current->comm, __func__,__LINE__,  (void *)__builtin_return_address(0));
+
+ exe_file = get_mm_exe_file(current->mm);
+
+ if (!exe_file)
+ {
+ printk("[-] path unknown: %s \n", current->comm);
+ return;
+ }
+
+ pathbuf = kmalloc(PATH_MAX, GFP_TEMPORARY);
+
+ if (!pathbuf) {
+ printk("[-] memory starved: %s \n", current->comm);
+ goto put_exe_file;
+ }
+
+ fname = d_path(&exe_file->f_path, pathbuf, PATH_MAX);
+
+ if (IS_ERR(fname)) {
+ printk("[-] path error: %s \n", current->comm);
+ goto free_buf;
+ }
+
+ printk("[+][%s]: exe_file: %s \n", current->comm, fname);
+
+free_buf:
+ kfree(pathbuf);
+put_exe_file:
+ fput(exe_file);
+ return;
+}
+
 asmlinkage void __exception do_undefinstr(struct pt_regs *regs)
 {
  unsigned int instr;
@@ -476,6 +515,12 @@ asmlinkage void __exception do_undefinstr(struct pt_regs *regs)
  return;
 
 die_sig:
+ printk("%s (%d): undefined instruction: pc=%p\n",
+ current->comm, task_pid_nr(current), pc);
+ __show_regs(regs);
+ dump_instr(KERN_INFO, regs);
+ trace_exe_filename();
+
 #ifdef CONFIG_DEBUG_USER
  if (user_debug & UDBG_UNDEFINED) {
  pr_info("%s (%d): undefined instruction: pc=%p\n",
@@ -553,6 +598,10 @@ static int bad_syscall(int n, struct pt_regs *regs)
  }
 #endif
 
+ pr_err("[%d] %s: obsolete system call %08x.\n",
+ task_pid_nr(current), current->comm, n);
+ dump_instr(KERN_ERR, regs);
+
  info.si_signo = SIGILL;
  info.si_errno = 0;
  info.si_code  = ILL_ILLTRP;
@@ -682,6 +731,17 @@ asmlinkage int arm_syscall(int no, struct pt_regs *regs)
  }
  }
 #endif
+
+ pr_err("[%d] %s: arm syscall %d\n",
+        task_pid_nr(current), current->comm, no);
+ dump_instr("", regs);
+ if (user_mode(regs)) {
+ __show_regs(regs);
+ c_backtrace(frame_pointer(regs), processor_mode(regs));
+ }
+
+ trace_exe_filename();
+
  info.si_signo = SIGILL;
  info.si_errno = 0;
  info.si_code  = ILL_ILLTRP;
diff --git a/arch/arm/mm/fault.c b/arch/arm/mm/fault.c
index 49b1b80..be205c9 100644
--- a/arch/arm/mm/fault.c
+++ b/arch/arm/mm/fault.c
@@ -167,6 +167,11 @@ __do_user_fault(struct task_struct *tsk, unsigned long addr,
  if (addr > TASK_SIZE)
  harden_branch_predictor();
 
+ printk(KERN_DEBUG "%s: unhandled page fault (%d) at 0x%08lx, code 0x%03x\n",
+        tsk->comm, sig, addr, fsr);
+ show_pte(tsk->mm, addr);
+ show_regs(regs);
+
 #ifdef CONFIG_DEBUG_USER
  if (((user_debug & UDBG_SEGV) && (sig == SIGSEGV)) ||
      ((user_debug & UDBG_BUS)  && (sig == SIGBUS))) {
diff --git a/arch/arm64/kernel/traps.c b/arch/arm64/kernel/traps.c
index 4cacc33d..41ed6f1 100644
--- a/arch/arm64/kernel/traps.c
+++ b/arch/arm64/kernel/traps.c
@@ -414,6 +414,7 @@ void arm64_notify_segfault(struct pt_regs *regs, unsigned long addr)
 
 asmlinkage void __exception do_undefinstr(struct pt_regs *regs)
 {
+ void __user *pc = (void __user *)instruction_pointer(regs);
  /* check for AArch32 breakpoint instructions */
  if (!aarch32_break_handler(regs))
  return;
@@ -421,6 +422,11 @@ asmlinkage void __exception do_undefinstr(struct pt_regs *regs)
  if (call_undef_hook(regs) == 0)
  return;
 
+ printk("+[F:%s, L:%d] caller(%pS)\n", __func__, __LINE__, (void *)__builtin_return_address(0));
+ printk("+%s[%d]: undef at 0x%08lx",
+ current->comm, task_pid_nr(current), (unsigned long)pc);
+ __show_regs(regs);
+
  force_signal_inject(SIGILL, ILL_ILLOPC, regs, 0);
 }
 
diff --git a/arch/arm64/mm/fault.c b/arch/arm64/mm/fault.c
index 465b90d..a4c53f0 100644
--- a/arch/arm64/mm/fault.c
+++ b/arch/arm64/mm/fault.c
@@ -303,6 +303,13 @@ static void __do_user_fault(struct task_struct *tsk, unsigned long addr,
  const struct fault_info *inf;
  unsigned int lsb = 0;
 
+ printk("+[F:%s, L:%d] caller(%pS)\n", __func__, __LINE__, (void *)__builtin_return_address(0));
+ printk("+%s[%d]: faults:(%d) at 0x%08lx, esr 0x%03x",
+ tsk->comm, task_pid_nr(tsk), sig,
+ addr, esr);
+
+ __show_regs(regs);
+
  if (unhandled_signal(tsk, sig) && show_unhandled_signals_ratelimited()) {
  inf = esr_to_fault_info(esr);
  pr_info("%s[%d]: unhandled %s (%d) at 0x%08lx, esr 0x%03x",
diff --git a/fs/exec.c b/fs/exec.c
index 0936b5a..ddfced9 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1666,6 +1666,34 @@ int search_binary_handler(struct linux_binprm *bprm)
 }
 EXPORT_SYMBOL(search_binary_handler);
 
+#define PROCESS_ARG_BUFF_SIZE 128
+
+void trace_process_exec_profile(const char *fname)
+{
+ char process_arg_buf[PROCESS_ARG_BUFF_SIZE] = {0,};
+ int len = 0;
+
+ len = get_cmdline(current, process_arg_buf, PROCESS_ARG_BUFF_SIZE -1);
+
+ if(len == 0) {
+ printk("[-] Error: len: %d \n", len);
+ return;
+ }
+
+ if(!fname) {
+ printk("[-] Error: fname null \n");
+ return;
+ }
+
+ if (len > PROCESS_ARG_BUFF_SIZE)
+ len = PROCESS_ARG_BUFF_SIZE;
+
+ process_arg_buf[len] = '\0';
+
+    pr_info("[exec:%s-%d] exec_file: %s, arg: [%s] \n",
+ current->comm, current->pid, fname, process_arg_buf);
+}
+
 static int exec_binprm(struct linux_binprm *bprm)
 {
  pid_t old_pid, old_vpid;
@@ -1683,6 +1711,8 @@ static int exec_binprm(struct linux_binprm *bprm)
  trace_sched_process_exec(current, old_pid, bprm);
  ptrace_event(PTRACE_EVENT_EXEC, old_vpid);
  proc_exec_connector(current);
+
+ trace_process_exec_profile(bprm->filename);
  }
 
  return ret;
diff --git a/fs/open.c b/fs/open.c
index 28a3956..f0bd689 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -1047,6 +1047,27 @@ struct file *filp_clone_open(struct file *oldfile)
 }
 EXPORT_SYMBOL(filp_clone_open);
 
+static void _trace_do_sys_open(struct file *filp, int flags, int mode, long fd)
+{
+       char *buf;
+       char *fname;
+
+       buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+       if (!buf)
+               return;
+
+       fname = d_path(&filp->f_path, buf, PAGE_SIZE);
+
+       if (IS_ERR(fname))
+               goto out;
+
+       printk("%s: open(\"%s\", %d, %d) fd = %ld, inode = %ld\n",
+                            current->comm, fname, flags, mode, fd, filp->f_inode->i_ino);
+
+out:
+       kfree(buf);
+}
+
 long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
 {
  struct open_flags op;
@@ -1069,6 +1090,7 @@ long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
  } else {
  fsnotify_open(f);
  fd_install(fd, f);
+ _trace_do_sys_open(f, flags, mode, fd);
  }
  }
  putname(tmp);
diff --git a/kernel/signal.c b/kernel/signal.c
index 619c616..b8d168a 100644
--- a/kernel/signal.c
+++ b/kernel/signal.c
@@ -1133,6 +1133,8 @@ static int __send_signal(int sig, struct siginfo *info, struct task_struct *t,
  complete_signal(sig, t, group);
 ret:
  trace_signal_generate(sig, info, t, group, result);
+ printk("[+][sig_gen][%s] sig = %d, target = %s \n",
+ current->comm, sig, t->comm);
  return ret;
 }
 
@@ -1628,6 +1630,8 @@ int send_sigqueue(struct sigqueue *q, struct task_struct *t, int group)
  result = TRACE_SIGNAL_DELIVERED;
 out:
  trace_signal_generate(sig, &q->info, t, group, result);
+ printk("[++][sig_gen][%s] sig = %d, target = %s \n",
+ current->comm, sig, t->comm);
  unlock_task_sighand(t, &flags);
 ret:
  return ret;
@@ -2312,6 +2316,9 @@ int get_signal(struct ksignal *ksig)
  /* Trace actually delivered signals. */
  trace_signal_deliver(signr, &ksig->info, ka);
 
+ printk("[+][signal_deliver][%s] sig = %d, \n",
+ current->comm, signr);
+
  if (ka->sa.sa_handler == SIG_IGN) /* Do nothing.  */
  continue;
  if (ka->sa.sa_handler != SIG_DFL) {
 
디바이스 트리를 관리하는 주요 자료 구조는 다음과 같습니다.
 
   * of_root
   * of_aliases
   * of_chosen
   * of_stdout
 
of_root
 
of_root 전역 변수는 다음과 같습니다.
 
  of_root = 0xF6B49F44 -> (
    name = 0xF6B4A16C -> "",
    type = 0xC157AEAB -> "<NULL>",
    phandle = 0,
    full_name = 0xF6B49F94 -> "/",
    properties = 0xF6B49F98,
    deadprops = 0x0,
    parent = 0x0,
    child = 0xF6B4A170 -> (
      name = 0xF6B4A264 -> "cpus",
      type = 0xC157AEAB -> "<NULL>",
      phandle = 0,
      full_name = 0xF6B4A1C0 -> "/cpus",  // '/cpu' 노드가 자식임
      properties = 0xF6B4A1C8,
      deadprops = 0x0,
      parent = 0xF6B49F44,
      child = 0xF6B4A26C,
      sibling = 0xF6B4DD28,
      kobj = (name = 0xF562FF00 -> "cpus", entry = (next = 0xF6B4A294, prev = 0xF6B49F6C), parent = 0xF6B49F68, kse
      _flags = 0,
      data = 0x0),
    sibling = 0x0,
    kobj = (name = 0xF562FC40 -> "base", entry = (next = 0xF6B4A198, prev = 0xF562FB40), parent = 0xF562FB4C, kset
    _flags = 16,
    data = 0x0)
 
 
0xF6B4A170 주소에 있는 '/cpu' 노드의 sibling 정보를 확인하면 '/soc'로 확인됩니다.
 
  of_root = 0xF6B49F44 -> (
    name = 0xF6B4A16C -> "",
    type = 0xC157AEAB -> "<NULL>",
    phandle = 0,
    full_name = 0xF6B49F94 -> "/",
...
    parent = 0x0,
    child = 0xF6B4A170 -> (
      name = 0xF6B4A264 -> "cpus",
      type = 0xC157AEAB -> "<NULL>",
      phandle = 0,
      full_name = 0xF6B4A1C0 -> "/cpus",
      properties = 0xF6B4A1C8,
      deadprops = 0x0,
      parent = 0xF6B49F44,
      child = 0xF6B4A26C,
      sibling = 0xF6B4DD28 -> (
        name = 0xF6B4DE84 -> "soc",
        type = 0xC157AEAB -> "<NULL>",
        phandle = 0,
        full_name = 0xF6B4DD78 -> "/soc",
        properties = 0xF6B4DD80,
        deadprops = 0x0,
        parent = 0xF6B49F44,
        child = 0xF6B4DE88,
        sibling = 0xF6BE855C,
...
 
'/soc'의 sibling 정보를 확인하면, 아래와 같습니다.
 
      sibling = 0xF6B4DD28 -> (
        name = 0xF6B4DE84 -> "soc",
        type = 0xC157AEAB -> "<NULL>",
        phandle = 0,
        full_name = 0xF6B4DD78 -> "/soc",
        properties = 0xF6B4DD80,
        deadprops = 0x0,
        parent = 0xF6B49F44,
        child = 0xF6B4DE88,
        sibling = 0xF6BE855C -> (
          name = 0xF6BE8754 -> "chosen",
          type = 0xC157AEAB -> "<NULL>",
          phandle = 0,
          full_name = 0xF6BE85AC -> "/chosen",
          properties = 0xF6BE85B4,
          deadprops = 0x0,
          parent = 0xF6B49F44,
          child = 0x0,
          sibling = 0xF6BE875C -> (
            name = 0xF6BE8BC8 -> "aliases",
            type = 0xC157AEAB -> "<NULL>",
            phandle = 0,
            full_name = 0xF6BE87AC -> "/aliases",
            properties = 0xF6BE87B8,
            deadprops = 0x0,
            parent = 0xF6B49F44,
            child = 0x0,
            sibling = 0xF6BE8BD0 -> (
              name = 0xF6BE8CC4 -> "memory",
              type = 0xC3437FD0 -> "memory",
              phandle = 0,
              full_name = 0xF6BE8C20 -> "/memory",
 
이 정보를 참고로 트리 구조는 다음과 같이 그릴 수 있습니다.
 
/
  |--- /cpus
  |--- /soc
  |--- /chosen
  |--- /aliases
  |--- /memory
  |--- /firmware
  |--- /reserved-memory
  |--- /psci
 
            sibling = 0xF6BE8BD0 -> (
              name = 0xF6BE8CC4 -> "memory",
              type = 0xC3437FD0 -> "memory",
              phandle = 0,
              full_name = 0xF6BE8C20 -> "/memory",
              properties = 0xF6BE8C28,
              deadprops = 0x0,
              parent = 0xF6B49F44,
              child = 0x0,
              sibling = 0xF6BE8CCC -> (
                name = 0xF6BE8D5C -> "firmware",
                type = 0xC157AEAB -> "<NULL>",
                phandle = 0,
                full_name = 0xF6BE8D1C -> "/firmware",
                properties = 0xF6BE8D28,
                deadprops = 0x0,
                parent = 0xF6B49F44,
                child = 0xF6BE8D68,
                sibling = 0xF6BE92DC -> (
                  name = 0xF6BE9410 -> "reserved-memory",
                  type = 0xC157AEAB -> "<NULL>",
                  phandle = 0,
                  full_name = 0xF6BE932C -> "/reserved-memory",
                  properties = 0xF6BE9340,
                  deadprops = 0x0,
                  parent = 0xF6B49F44,
                  child = 0xF6BE9420,
                  sibling = 0xF6BEAA14 -> (
                    name = 0xF6BEAB08 -> "psci",
                    type = 0xC157AEAB -> "<NULL>",
                    phandle = 0,
                    full_name = 0xF6BEAA64 -> "/psci",
                    properties = 0xF6BEAA6C,
 
TRACE32를 사용하다 보면 메모리 덤프에서 특정 데이터를 검색하고 싶은 경우가 있습니다.
이 때는 data.find 명령어를 사용하면 됩니다.
 
다음은 TRACE32에서 릴리즈한 data.find 관련 스팩 문서(general_ref_d.pdf)의 내용입니다.
 
Format: Data.Find [<address_range> [%<format>] <data> | <string> [/<option>]]
<format>: Byte | Word | Long | Quad | TByte | HByte | Float .<format> | BE | LE
<option>: Back | NoFind
 
; search for byte 0x3f in the specified address range
Data.Find 0x100--0xfff 0x3f
 
; search the next byte x3f
Data.Find
 
; search for specified string
Data.Find 0x100--0xfff "Test"
 
; search for 32 bit value 0x00001234 in big endian mode
Data.Find 0x100++0xeff %Long %BE 0x1234
 
; search backward for 16 bit value 0x0089
Data.Find 0x100++0xeff %Word 0x89 /Back
 
; search for the float 1.45678 in IEEE format
Data.Find 0x4e00--0x4eff %Float.Ieee 1.45678
 
자, 그럼 0x80100000와 0x80200000 메모리 구간에 0xDEADBEEF 데이터를 검색하려면, 어떤 명령어를 입력해야 해야 할까요? 다음과 같이 입력하면 됩니다.
 
data.find 0x80100000--0x80200000 0xDEADBEEF
 
위와 같이 명령어를 입력하면 TRACE32에서 다음과 같은 결과 화면을 볼 수 있습니다.
 
 
 
Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자
 
 
 
 
아래 cmm 을 실행하면 디바이스에 존재하는 struct device 인스턴스 정보를 모두 볼 수 있습니다.
 
;****************************************************************************
;**         dpm_device_list_dump.cmm
;**
;**         This cmm file is designed to dump all  dpm_list.
;**
;**
;****************************************************************************
;**
;**                        EDIT HISTORY FOR MODULE
;**
;**
;** when        who      what, where, why
;** --------    ---      ------------------------------------------------------
;** 01/30/2021   austindh.kim@gmail.com CREATE
 
Area.Create IO 80. 100.
Area.Select IO
Area IO
 
;=====specify the output directory of dump file, start =====
DIALOG.DIR D:\kernel_panic\*
ENTRY &ramdump_dir
 
&dump_debug_file="&ramdump_dir"+"/dpm_device_debug.c"
 
printer.FILE &dump_debug_file
printer.OPEN &dump_debug_file
 
&CURRENT_DPM_LIST=0x0
&FIRST_DPM_LIST=0x0
 
//offset of struct device.power.entry
&OFFSET_DEVICE_POWER_ENTRY=0x0
 
&CURRENT_DEVICE_ADDRESS=0x0
 
&LOOP_CONDITION=0x0
 
// default symbols, start
y.create.var __guillerMo_char_start 0xc000d000 char [300]
y.create.done
 
y.create.var __guillerMo_char_end 0xc000e000 char [300]
y.create.done
 
y.create.var __guillerMo_char_dpm_noirq 0xc000f000 char [300]
y.create.done
 
 
v __guillerMo_char_start="============= START ==========="
v __guillerMo_char_end="============== END ============"
v __guillerMo_char_dpm_noirq="@@@@@@@@DPM_NOIRQ_LIST@@@@@@@@@@@"
// default symbols, end
 
y.create.var __guillerMo_dpm_list 0 struct list_head
y.create.done
 
y.create.var __guillerMo_device 0 struct device
y.create.done
 
symbol.modify.address __guillerMo_dpm_list VAR.VALUE(&dpm_list)
 
gosub getDeviceOffsetAddress
 
&FIRST_DPM_LIST=VAR.VALUE(&dpm_list)
&CURRENT_DPM_LIST=&FIRST_DPM_LIST
 
while &LOOP_CONDITION!=0x7
(
&CURRENT_DEVICE_ADDRESS=&CURRENT_DPM_LIST-&OFFSET_DEVICE_POWER_ENTRY
symbol.modify.address __guillerMo_device &CURRENT_DEVICE_ADDRESS
 
// start logging display struct device*
wp.v.v %string.on __guillerMo_char_start
 
wp.v.v %string.on __guillerMo_device.kobj.name
wp.v.v %all %L __guillerMo_device
 
wp.v.v %string.on __guillerMo_char_end
// end logging display struct device*
&CURRENT_DPM_LIST=V.VALUE( ((struct list_head *)&CURRENT_DPM_LIST)->next )
 
if &CURRENT_DPM_LIST==&FIRST_DPM_LIST
(
&LOOP_CONDITION=0x7
)
)
 
// start logging dpm_noirq_list for struct device*
wp.v.v %string.on __guillerMo_char_dpm_noirq
 
&FIRST_DPM_LIST=VAR.VALUE(&dpm_noirq_list)
&CURRENT_DPM_LIST=&FIRST_DPM_LIST
 
&LOOP_CONDITION=0x0
 
while &LOOP_CONDITION!=0x7
(
&CURRENT_DEVICE_ADDRESS=&CURRENT_DPM_LIST-&OFFSET_DEVICE_POWER_ENTRY
symbol.modify.address __guillerMo_device &CURRENT_DEVICE_ADDRESS
 
// start logging display struct device*
wp.v.v %string.on __guillerMo_char_start
 
wp.v.v %string.on __guillerMo_device.kobj.name
wp.v.v %all %L __guillerMo_device
 
wp.v.v %string.on __guillerMo_char_end
// end logging display struct device*
&CURRENT_DPM_LIST=V.VALUE( ((struct list_head *)&CURRENT_DPM_LIST)->next )
 
if &CURRENT_DPM_LIST==&FIRST_DPM_LIST
(
&LOOP_CONDITION=0x7
)
)
 
 
printer.close
 
y.create.RESET
enddo
 
; *************************************************************************
; getDeviceOffsetAddress
;
; This function determines offset of struct device.power.entry
; *************************************************************************
getDeviceOffsetAddress:
  LOCAL &local_power_entry  &local_power_offset
  
  &local_power_entry=0x0
  &local_power_offset=0x0
  
  symbol.modify.address __guillerMo_device VAR.VALUE(&dpm_list)
  
  &local_power_entry=V.ADDRESS(__guillerMo_device.power.entry)
  &local_power_offset=&local_power_entry-V.ADDRESS(__guillerMo_device)
  
  &OFFSET_DEVICE_POWER_ENTRY=&local_power_offset
  
RETURN
// end of getDeviceOffsetAddress
이번 포스트에서는 GDB를 사용해 디버깅을 하는 방법을 소개합니다. 소개된 내용을 참고하면 즐겁게 어셈블리 명령어를 디버깅할 수 있습니다.
 
환경: 라즈베리 파이4
 
GDB 실행
 
아래 명령어를 사용해 GDB를 Text User Interface 모드로 실행합니다.
 
$ gdb -tui armv7_aapcs_proc
 
다음은 위 명령어로 실행한 화면입니다.
 
 
이제 바로 다음 명령어를 입력해 어셈블리 명령어 창을 보이도록 합시다.
 
$ layout split
 
 
 
이번에는 'b main'와 'r' 명령어를 입력해 main() 함수에 브레이크 포인트를 걸고 프로그램을 실행합니다.
 
 
 
위 명령어를 입력하면 다음과 같은 화면이 보입니다.
 
 
 
위 화면과 같이 main() 함수의 첫 번째 라인에 브레이크 포인트가 걸립니다.
 
C 코드 한 줄 실행
 
자, 이어서 'n' 명령어를 입력해 C 코드 기준으로 소스 한 줄을 실행합시다.
 
 
C 코드 기준으로는 19번째 줄, 어셈블리 코드 기준으로 0x10490 주소까지 실행됐습니다.
 
어셈블리 명령어 기준 실행
 
이번에는 'si' 명령어를 입력해 어셈블리 코드를 라인 바이 라인(line-by-line)으로 실행합시다.
 
 
특이한 점은 어셈블리 코드 기준으로 브레이크 포인트가 움직이는데, C 소스 기준으로 브레이크 포인트는 변하지 않을 때가 많다는 점입니다. 이는 C 코드 1줄을 2~3개 어셈블리 명령어로 실행하기 때문입니다.
 
레지스터 창 출력하기 
 
이번에는 레지스터 정보를 확인하기 위해 'info reg' 명령어를 입력합시다.
 
 
SP(스택 포인터) 레지스터는 0xbefff5d0, LR(링크 레지스터)는 0xb6e6a718 주소를 저장합니다.
 
레지스터를 확인하기 위해 'layout reg' 명령어를 입력해 보겠습니다.
 
 
레지스터와 어셈블리 명령어가 출력됩니다. 이 상태로 계속 디버깅을 할 수 있으나, C 코드가 보이지 않으니 조금 답답합니다. 이 때 'layout split' 명령어를 다시 입력하면 C 코드와 어셈블리 명령어를 같이 볼 수 있는 창으로 변환됩니다.
 
이번에도 'si' 명령어를 입력해 어셈블리 명령어 'ldr    r0, [r11, #-8]'를 실행합니다.
 
 
이제 'bl     0x10434 <add_func>' 명령어를 실행해 add_func() 함수를 호출하기 직전의 시점입니다.
 
Armv7 아키텍처의 동작 원리를 배우며 디버깅해보기
 
여기서 한 가지 눈여겨 볼 부분이 있습니다. 'bl     0x10434 <add_func>' 명령어 아랫 부분의 주소가 0x104a4인데 add_func() 함수를 호출하면 복귀할 주소입니다.
 
그런데 add_func() 함수가 실행해 add_func() 함수의 시작 주소로 PC가 바뀌면 lr은 0x104a4 주소로 바뀔 것입니다. 이 점을 예상하고 'bl     0x10434 <add_func>' 명령어를 실행하기 직전의 레지스터 정보를 확인해 봅시다. (이를 위해 'info reg' 명령어를 입력했습니다.)
 
확인하니 lr(r14) 레지스터는 0xb6e6a718를 담고 있습니다.
 
이번에는 si 명령어를 입력해 'bl     0x10434 <add_func>' 명령어를 실행하겠습니다.
 
 
위 화면과 같이 add_func() 함수의 첫 번째 코드에 브레이크 포인트가 걸려 있습니다.
 
자, 이 시점에서 lr(r14) 레지스터를 확인해볼까요? 확인하니 lr(r14) 레지스터는 0xb6e6a718를 담고 있습니다.
 
이 방법을 활용해 GDB를 실행하면, Arm 어셈블리 명령어를 실행하면 레지스터가 어떻게 바뀌는지 직접 눈으로 확인할 수 있습니다.
 
Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자
 
 
 
 

+ Recent posts