Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

11206
629
98793


android - recovery [Bootloader]

안드로이드 폰에서 Recovery 모드란 들어보신 적이 있나요? 
휴대폰 매장 직원도 종종 쓰는 표현인 것 같아요.
 
Recovery란  안드로이드 시스템에서 제공하고 있는 중요 기능 중에 하나랍니다.
이 기능에 대해서 좀 살펴볼까요?

보통 디바이스에 Recovery를 실행시키기 위한 파티션이 따로 있고, 부트로더(대부분 LK:Little Kernel)에서 특정 조건으로 Recovery를 실행시켜요.
그 조건은 팩토리 리셋을 시킨다던가, 다운로드 모드에 진입하는 조건들이죠.

그럼 Recovery 이미지를 생성하는 재료는 어떻게 구성될까요?
아래 MakeFile이 정답을 말해주고 있네요.
[android/build/core/Makefile]
$(INSTALLED_RECOVERYIMAGE_TARGET): $(MKBOOTFS) $(MKBOOTIMG) $(MINIGZIP) \
$(INSTALLED_RAMDISK_TARGET) \ // $(PRODUCT_OUT)/ramdisk.img //<<--[1]
$(INSTALLED_BOOTIMAGE_TARGET) \ // $(PRODUCT_OUT)/boot.img //<<--[2]
$(INTERNAL_RECOVERYIMAGE_FILES) \  // android/bootable/recovery //<<--[3]
$(recovery_initrc) $(recovery_sepolicy) $(recovery_kernel) \ //<<--$(call include-path-for, recovery)/etc/init.rc
$(INSTALLED_2NDBOOTLOADER_TARGET) \
$(recovery_build_props) $(recovery_resource_deps) \
$(recovery_fstab) \
$(RECOVERY_INSTALL_OTA_KEYS) \
$(INSTALLED_VENDOR_DEFAULT_PROP_TARGET) \
$(BOARD_RECOVERY_KERNEL_MODULES) \
$(DEPMOD)
$(call build-recoveryimage-target, $@)

크게 아래 이미지로 구성된다고 보면 되죠.
1> 램디스크 이미지(루프 파일 시스템)
2> 커널 이미지
3> android/bootable/recovery 에 있는 코드들
4> android/bootable/recovery/etc/init.rc

그럼 어떤 놈이 recovery를 실행시킬까요? android/bootable/recovery/etc/init.rc 코드를 열어서 보면 상세 정보를 알 수 있어요.
아래 코드를 보면 init process와 파일 시스템을 마운트하고 recovery를 서비스 형태로 실행시키죠.
4on early-init
5    # Set the security context of /postinstall if present.
6    restorecon /postinstall
7
8    start ueventd
9
10on init
11    export ANDROID_ROOT /system
12    export ANDROID_DATA /data
13    export EXTERNAL_STORAGE /sdcard
14
15    symlink /system/etc /etc

112service recovery /sbin/recovery
113    seclabel u:r:recovery:s0

보통 안드로이드 모바일에서 Recovery 모드는 3가지 시나리오 지원해요.
1> 세팅 메뉴에서 팩토리 리셋을 선택하였을 때 리부팅 된 다음에 진입
2> 전원이 꺼진 상태에서 키맵(제조사 마다 다르죠)으로 팩토리 리셋에 진입
3> GOTA upgrade

가끔 Recovery 모드에서 팩토리 리셋이 제대로 안된다 등 화면이 깨진다 등등 이슈가 있다고 연락이 와요.
이럴 때 보통 아래 폴더에 있는 로그를 열고 디버깅을 할 수 있죠.
/cache/recovery/last_log, last_log.2

아래 recovery 모드 실행 시 아래 로그를 볼 수 있어요.
...
locale is [en_US]
stage is []
failed to read font: res=-1
framebuffer: fd 4 (800 x 1280)
       installing_text: en (324 x 38 @ 411)
          erasing_text: en (109 x 38 @ 312)
       no_command_text: en (172 x 38 @ 312)
            error_text: en (64 x 38 @ 312)
I:Max_satage-1: 17299
I:SetBackGround - : 50
I:Max_satage-2: 17299
Command: "/sbin/recovery"

그러면 Recovery mode에서 주로 쓰이는 Factoy Reset Feature에 대해서 좀 살펴볼까요?
팩토리 리셋은 크게 세팅 메뉴로 동작시킬 수 있고, 각 제조사가 정의한 모드를 통해 동작시킬 수 있습니다.

세팅 메뉴에서 팩토리 리셋을 선택할 시 동작 순서는 아래와 같아요.
1> /cache/recovery/command 파일을 만들고 여기에 argument 값 --wipe_data 를 씀
2> 리부팅 됨
3> LK(Little Kernel)에서 misc 파티션에 아래와 같은 값을 써줌
      bootMessage.command="boot-recovery"
      bootMessage.status[0] = (char)0;
      bootMessage.recovery="recovery\n--wipe_data\n"
4> 리커버리가 실행(android/bootable/recovery)가 되고 /cache/recovery/command 파일을 찾아서 --wipe_data 값이 있는 지 확인
5> /cache, /data 파티션을 날려 버림

리커버리 모드 코드에서 파악할 중요한 포인트는 어떻게 argument을 읽어오며 이에 따라 어떤 동작을 하는 지,
각 파티션은 어떻게 날려버리는 지가 중요하죠.

대표적인 동작 별 argument는 아래와 같습니다.
팩토리 리셋: --wipe_out, GOTA: --update_package 

이제 코드 좀 볼까요? 각 bootloader나 커널의 실행 시작 포인트는 reset vector인데 
데몬 형태의 프로세스들은 main으로 시작하죠.

[1]: 나 리커버리, 시작된다!
[2]: boot argument를 읽어오자.
[3]: boot argument값에 따라 recovery 동작에 대한 정의
[4]: recovery mode 동작 이후 디바이스를 shutdown 시키는 argument
[5]: 언어 모드를 설정
[6][7]: 화면에 뿌릴 UI와 text를 초기화
[8]: argument를 로그에 찍어 줌
[9]: property를 로그로 출력
[10]: update_package이 NULL이면 실행 안함
[11]: 밧데리 레벨이 낮으면 튕겨낼 코드, GOTA package 업데이트 중 밧데리 방전되면 참 골치 아프지.
[12]: GOTA package update
[13]: 팩토리 리셋
[14]: 패키지 업데이트 도중 Fail 시 reboot을 칠 예외 코드
[15]: 팩토리 리셋
[16]: 디바이스 셧다운
[17]: 부트로더로 다시 리부팅
[18]: 디폴트로 리부팅 시킴 
int main(int argc, char **argv) {
    // We don't have logcat yet under recovery; so we'll print error on screen and
    // log to stdout (which is redirected to recovery.log) as we used to do.
    android::base::InitLogging(argv, &UiLogger);
// ... skip ...
    printf("Starting recovery (pid %d) on %s", getpid(), ctime(&start));  //<<--[1]

    load_volume_table();
    has_cache = volume_for_path(CACHE_ROOT) != nullptr;
//.. skip ..
    std::vector<std::string> args = get_args(argc, argv); //<<--[2]
    while ((arg = getopt_long(args_to_parse.size(), args_to_parse.data(), "", OPTIONS,
                              &option_index)) != -1) {
        switch (arg) {  //<<--[3]
        case 'n': android::base::ParseInt(optarg, &retry_count, 0); break;
        case 'u': update_package = optarg; break;
        case 'w': should_wipe_data = true; break;
        case 'c': should_wipe_cache = true; break;
        case 't': show_text = true; break;
        case 's': sideload = true; break;
        case 'a': sideload = true; sideload_auto_reboot = true; break;
        case 'x': just_exit = true; break;
        case 'l': locale = optarg; break;
        case 'p': shutdown_after = true; break;  //<<--[4]
        case 'r': reason = optarg; break;
        case 'e': security_update = true; break;
        case 'f': fota_mode = true; break;
    }

    if (locale.empty()) { //<<--[5], start
        if (has_cache) {
            locale = load_locale_from_cache();
        }

        if (locale.empty()) {
            locale = DEFAULT_LOCALE;
        }
    } //<<--[5], end

    printf("locale is [%s]\n", locale.c_str());
    printf("stage is [%s]\n", stage.c_str());
    printf("reason is [%s]\n", reason);

    Device* device = make_device();
    if (android::base::GetBoolProperty("ro.boot.quiescent", false)) {
        printf("Quiescent recovery mode.\n");
        ui = new StubRecoveryUI();
    } else {
        ui = device->GetUI(); 

        if (!ui->Init(locale)) {
            printf("Failed to initialize UI, use stub UI instead.\n");
            ui = new StubRecoveryUI();
        }
    }

    // Set background string to "installing security update" for security update,
    // otherwise set it to "installing system update".
    ui->SetSystemUpdateText(security_update); //<<--[6]
// ... skip ...
    ui->SetBackground(RecoveryUI::NONE); //<<--[7]
    if (show_text) ui->ShowText(true);
// ... skip ...

    sehandle = selinux_android_file_context_handle();
    selinux_android_set_sehandle(sehandle);
    if (!sehandle) {
        ui->Print("Warning: No file_contexts\n");
    }

    device->StartRecovery();

    printf("Command:");
    for (const auto& arg : args) {
        printf(" \"%s\"", arg.c_str()); //<<--[8]
    }
    printf("\n\n");

    property_list(print_property, NULL); //<<--[9]
    printf("\n");

// ... skip ...

    if (update_package != NULL) {  //<<--[10]
        // It's not entirely true that we will modify the flash. But we want
        // to log the update attempt since update_package is non-NULL.
        modified_flash = true;

        if (!is_battery_ok()) { //<<--[11]
            ui->Print("battery capacity is not enough for installing package, needed is %d%%\n",
                      BATTERY_OK_PERCENTAGE);
            // Log the error code to last_install when installation skips due to
            // low battery.
            log_failure_code(kLowBattery, update_package);
            status = INSTALL_SKIPPED;
        } else if (bootreason_in_blacklist()) {
            // Skip update-on-reboot when bootreason is kernel_panic or similar
            ui->Print("bootreason is in the blacklist; skip OTA installation\n");
            log_failure_code(kBootreasonInBlacklist, update_package);
            status = INSTALL_SKIPPED;
        } else {
            status = install_package(update_package, &should_wipe_cache, //<<--[12]
                                     TEMPORARY_INSTALL_FILE, true, retry_count);
            if (status == INSTALL_SUCCESS && should_wipe_cache) {
                wipe_cache(false, device); //<<--[13]
            }
            if (status != INSTALL_SUCCESS) {
                ui->Print("Installation aborted.\n");
                // When I/O error happens, reboot and retry installation EIO_RETRY_COUNT
                // times before we abandon this OTA update.
                if (status == INSTALL_RETRY && retry_count < EIO_RETRY_COUNT) {
                    copy_logs();
                    set_retry_bootloader_message(retry_count, args);
                    // Print retry count on screen.
                    ui->Print("Retry attempt %d\n", retry_count);

                    // Reboot and retry the update
                    if (!reboot("reboot,recovery")) { //<<--[14]
                        ui->Print("Reboot failed\n");
                    } else {
                        while (true) {
                            pause();
                        }
                    }
                }
// ... skip ...
    } else if (should_wipe_data) {
        if (!wipe_data(device)) { //<<--[15]
            status = INSTALL_ERROR;
        }

    // Save logs and clean up before rebooting or shutting down.
    finish_recovery();

    switch (after) {
        case Device::SHUTDOWN:  //<<--[16]
            ui->Print("Shutting down...\n");
            android::base::SetProperty(ANDROID_RB_PROPERTY, "shutdown,");
            break;

        case Device::REBOOT_BOOTLOADER: //<<--[17]
            ui->Print("Rebooting to bootloader...\n");
            android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,bootloader");
            break;

        default:
            ui->Print("Rebooting...\n"); //<<--[18]
            reboot("reboot,");

Recovery를 디버깅할 때 가장 중요한 함수는 get_args() 이거든요.
좀 더 상세히 살펴볼까요?
[1]: misc 파티션에서 boot message를 읽어와요.
[2]: 부트 커맨드에 대한 로그를 뿌려요.
[3]: \n 단위로 스트링을 짤라줘요.
  argument가 “recovery \n --wipe_data” 인 경우,
  이렇게 되는 거죠. tokens[0] = recovery, tokens[1] = --wipd_data
[4]: COMMAND_FILE를 읽어서 처리를 하는 루틴인데, 세팅 메뉴로 팩토리 리셋을 선택할 경우 동작하죠.   
static std::vector<std::string> get_args(const int argc, char** const argv) {
  CHECK_GT(argc, 0);

  bootloader_message boot = {};
  std::string err;
  if (!read_bootloader_message(&boot, &err)) { //<<--[1]
    LOG(ERROR) << err;
    // If fails, leave a zeroed bootloader_message.
    boot = {};
  }
  stage = std::string(boot.stage);

  if (boot.command[0] != 0) {
    std::string boot_command = std::string(boot.command, sizeof(boot.command));
    LOG(INFO) << "Boot command: " << boot_command; //<<--[2]
  }

  if (boot.status[0] != 0) {
    std::string boot_status = std::string(boot.status, sizeof(boot.status));
    LOG(INFO) << "Boot status: " << boot_status;
  }

  std::vector<std::string> args(argv, argv + argc);

  // --- if arguments weren't supplied, look in the bootloader control block
  if (args.size() == 1) {
    boot.recovery[sizeof(boot.recovery) - 1] = '\0';  // Ensure termination
    std::string boot_recovery(boot.recovery);
    std::vector<std::string> tokens = android::base::Split(boot_recovery, "\n");
    if (!tokens.empty() && tokens[0] == "recovery") { 
      for (auto it = tokens.begin() + 1; it != tokens.end(); it++) {  //<<--[3]
        // Skip empty and '\0'-filled tokens.
        if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it));
      }
      LOG(INFO) << "Got " << args.size() << " arguments from boot message";
    } else if (boot.recovery[0] != 0) {
      LOG(ERROR) << "Bad boot message: \"" << boot_recovery << "\"";
    }
  }

  // --- if that doesn't work, try the command file (if we have /cache).
  if (args.size() == 1 && has_cache) {
    std::string content;
    if (ensure_path_mounted(COMMAND_FILE) == 0 &&  //<<--[4]
        android::base::ReadFileToString(COMMAND_FILE, &content)) {
      std::vector<std::string> tokens = android::base::Split(content, "\n");
      // All the arguments in COMMAND_FILE are needed (unlike the BCB message,
      // COMMAND_FILE doesn't use filename as the first argument).
      for (auto it = tokens.begin(); it != tokens.end(); it++) {
        // Skip empty and '\0'-filled tokens.
        if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it));
      }
      LOG(INFO) << "Got " << args.size() << " arguments from " << COMMAND_FILE;
    }
  }

마지막으로 misc 파티션에 명시된 recovery argument을 읽어와 boot.command 변수에 붙혀주는 
함수는 write_bootloader_message() 이네요.
예를 들어 이렇게? boot.recovery = "recovery"+"--wipe_data
bool write_bootloader_message(const std::vector<std::string>& options, std::string* err) {
  bootloader_message boot = {};
  strlcpy(boot.command, "boot-recovery", sizeof(boot.command));
  strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery));
  for (const auto& s : options) {
    strlcat(boot.recovery, s.c_str(), sizeof(boot.recovery)); //<<--
    if (s.back() != '\n') {
      strlcat(boot.recovery, "\n", sizeof(boot.recovery));
    }
  }
  return write_bootloader_message(boot, err);
}  


Reference(프로세스 관리)
4.9 프로세스 컨택스트 정보는 어떻게 저장할까?
 4.9.1 컨택스트 소개
 4.9.2 인터럽트 컨택스트 정보 확인하기
 4.9.3 Soft IRQ 컨택스트 정보 확인하기
 4.9.4 선점 스케줄링 여부 정보 저장
4.10 프로세스 디스크립터 접근 매크로 함수
 4.10.1 current_thread_info()
 4.10.2 current 매크로란
4.11 프로세스 디버깅
 4.11.1 glibc fork 함수 gdb 디버깅
 4.11.2 유저 프로그램 실행 추적 


덧글

댓글 입력 영역