안녕하세요, 타이젠 개발자 윤진입니다.


본 포스팅은 이틀에 걸쳐 졸음과 싸워가며 작성한 포스팅입니다.

오탈자가 있을 수 있으니 너그러운 맘으로 지적질(?) 부탁드립니다;


사실 이전 포스팅에서도 타이젠 개발환경의 핵심툴인 sdb를 언급한 적이 있습니다.

- sdb 설치하기 : [Tizen] 우분투에 타이젠 플랫폼 툴인 gbs & sdb 설치해보기

- sdb 사용하기 with Gear S2 : [Tizen/Gear S2] 타이젠 기어S2와 호스트 PC 연결하기


하지만 하루에도 수십차례 이용하는 sdb의 주요 기능에 대해 다룬 적이 없더군요.

어쩌면 지금 이 시간에도 수많은 타이젠 개발자들이 sdb를 이용하고 있을텐데요.

이 포스팅에서는 타이젠 개발자들이 주로 사용하는 sdb 기능을 설명하도록 하겠습니다.


sdb는 개발시스템과 디바이스(혹은 에뮬)을 연결하여 사용할 수 있도록 도와주는 툴입니다.

개발시스템과 에뮬 혹은 디바이스 혹은 에뮬과 디바이스를 동시에 연결할 수도 있지요.

파일을 주고 받고, 원격 쉘 접속을 위해 sdb를 사용하고 있습니다.


이런 sdb를 사용하기 위해서는 2가지 전제조건이 있습니다.


전제 1. 당연히, sdb를 설치하셔야겠지요.

sdb는 기본적으로 Tizen SDK와 함께 설치됩니다.

따라서 Tizen SDK를 설치하셨다면,

tizen-sdk/tools/sdb를 찾으실 수 있을겁니다.

만약 SDK 없이 sdb 툴만 리눅스환경에서 따로 받고 싶으시면 위에 언급한 포스팅을 참고해주세요.


전제 2. sdb로 붙이려는 디바이스의 debug 모드를 "On" 하셔야합니다.

디바이스에서 debug 모드를 켜놓지 않으면 sdb를 붙일 수 없지요.

타이젠 플랫폼에서는 Home > Settings > Device Info. > USB Debugging에서 설정하고,

타이젠 상품 Gear 시리즈에서는 Apps > Settings > Gear Info. > Debugging에서 설정하며,

타이젠 상품 Z1, Z3에서는 아래 포스팅을 참고해서 debug 모드를 설정하실 수 있습니다.

[Tizen] 타이젠 Z1에 개발자모드 메뉴가 숨겨져 있는 까닭은?


전제조건을 모두 만족하셨다면 이제 sdb 명령어를 살펴볼 차례입니다.

sdb 명령어는 도스나 리눅스 쉘에서 직접 사용할 수 있습니다.


sdb [option] <command> [parameters]


option에는 총 3가지 명령어가 있습니다.

옵션 1. "-d" 디바이스와 연결할 때 사용하는 명령어입니다.

개발시스템과 연결한 장치가 여러개 있을 경우,

그 중 USB와 직접 연결된 디바이스에 -d 옵션으로 접속할 수 있습니다.


옵션 2. "-e" 에뮬레이터와 연결할 때 사용하는 명령어입니다.

개발시스템과 연결한 장치가 여러개 있을 경우,

그 중 에뮬레이터에 -e 옵션으로 접속할 수 있습니다.

   

옵션 3. "-s" 시리얼넘버로 디바이스 혹은 에뮬에 접속할 수 있습니다.

시리얼 넘버는 sdb get-serialno 명령어로 얻을 수 있는데요,

앱개발할 때 사용한 경험이 거의 없군요 :)


위의 명령어 세가지는 말 그대로 옵션입니다.

개발시스템에 에뮬레이터와 디바이스가 모두 연결되어 있고,

둘 중 하나를 번걸아가며 접속할 때 유용하게 사용할 수 있습니다.

에뮬과 디바이스 중 하나만 접속이 되어 있다면,

옵션을 사용하지 않아도 접속된 에뮬 혹은 디바이스에 알아서 접속해줍니다.


command에는 다수의 유용한 명령어가 있습니다.

명령어 1. "root" <on | off>

root 명령어는 sdb로 에뮬 혹은 디바이스에 루트 권한으로 접속하게 해줍니다.

에뮬이나 플랫폼 바이너리가 탑재된 TM1 시료에서 사용할 수 있습니다.

Z1, Z3, Gear1, Gear2와 같은 상품에서는 root 권한을 얻을 수 없습니다.


명령어 2. "connect" <host>[:<port>]

connect는 Gear S2를 연결할 때 사용합니다.

이 명령어는 아래 포스팅을 참고해주세요.

[Tizen/Gear S2] 타이젠 기어S2와 호스트 PC 연결하기


명령어 3. "shell"

shell 명령어는 리모트쉘로 에뮬 혹은 디바이스에 접속할 수 있게 해줍니다.

쉘 명령어로 장치에 접속을 한뒤,

플랫폼에 설치된 다양한 명령어를 사용하여 장치의 상태를 확인할 수 있습니다.

top 혹은 ps를 사용하여 장치상태를 확인해보세요 :)


굳이 리모트 쉘에 접속할 필요없이,

쉘명령어만 한 번 사용하여 결과를 보고 싶다면,

sdb shell <명령어> 형식을 사용하면 됩니다.

예를 들어 ps 상황만 체크하고 싶다면,

sdb shell ps를 치면 쉘없이 ps 내용이 바로 출력됩니다.


명령어 4. "install" <pkg_path>

개발시스템에서 빌드한 패키지를 remote로 복사한 후 설치합니다.

패키지만 가지고 있는 경우,

sdb 명령어를 통해 쉽게 복사 & 설치할 수 있겠네요.


명령어 5. "uninstall" <pkg_id>

remote에서 패키지를 삭제할 때 사용할 수 있습니다.

이 때 pkg_id를 정확하게 알아야합니다.

자신이 삭제할 패키지의 ID는 정확히 알아야겠지요?


명령어 6. "push" <local> <remote>

개발시스템에서 빌드한 패키지를 에뮬 혹은 디바이스에 보낼 때 주로 사용합니다.

패키지 뿐만 아니라 각종 파일을 넘기는데 사용하지요.

<local>에는 개발시스템에서 보낼 파일을 기입합니다.

<remote>에는 파일을 받을 장소를 선택합니다.

만약 파일을 받을 디렉토리가 없다면 제대로 푸시되지 않을 수 있으니 미리 만들어주세요.

# sdb push file_to_push.txt /home/developer

위와 같이 file_to_push.txt 파일을 /home/developer 위치에 넣을 수 있습니다.


명령어 7. "pull" <remote> [<local>]

push와 상반되게 리모트 시스템에 있는 파일을 개발시스템으로 가져올 수 있습니다.

<remote>에는 리모트 시스템에 있는 파일을 절대경로로 적어주면 됩니다.

[<local>]을 적지 않으면 현재 디렉토리로 파일을 가져옵니다.


명령어 8. "kill-server" & "start-server"

sdb로 제대로 접속이 안될 경우,

sdb server를 죽였다가 다시 살립니다.

개발시스템과 에뮬 혹은 디바이스가 대부분 제대로 잘 붙는데요,

아주 간헐적으로 알 수 없는 이유 때문에 잘 안붙을 때가 있습니다.

그럴때 서버를 죽였다가 살려보세요. :)


명령어 9. "get-serialno" & "devices"

현재 접속된 디바이스 혹은 에뮬에 대한 시리얼 넘버를 알려줍니다.

여기서 얻은 값을 보고 위에서 설명드린 -s 옵션과 함께 사용하면 됩니다.


명령어 10. "dlog" [<filter_spec>]

개발시스템에 접속한 리모트장치의 상태를 dlog를 통해 엿볼 수 있습니다.

# sdb dlog

위의 명령어로 쉽게 로그를 출력할 수 있지요 :)


이상과 같이 간단하게 sdb의 명령어를 살펴봤습니다.

그럼 오늘도 즐거운 개발하시길... :)


끝_

개발자로서 살아온 기간내내

함수 안팎으로 로그를 '신나게' 심어왔습니다.


코드에 로그가 없는 경우는,

- 아직 로그를 심기 직전이거나

- 동작을 확인하고 로그를 지운 뒤겠죠.


앱이 물고 있는 프레임워크의 동작이 언제나 완벽하다면,

앱에서 로그를 출력할 일은 크게 줄어들 것입니다.


하지만, 언제나 프레임워크는 개발막바지가 되어야 비로소 쓸만해지죠. :)

모든 함수에 에러체크는 필수이고,

에러로그는 가장 쉬우면서 확실한 디버깅 방법입니다.


안녕하세요, Tizen 개발자 윤진입니다.


타이젠은 플랫폼 차원에서 로그를 남길 수 있는 방법을 제공합니다.

printf(), fprintf()도 사용할 수 있지만,

- SDK에 로그를 노출하거나,

- 시스템 로그들과 함께 사용하기 위해서는,

타이젠에서 제공하는 dlog를 사용해야 합니다.


타이젠 개발자 사이트(http://developer.tizen.org)에 들어가서,

하단의 API References 메뉴에 들어갑니다.



왼편에 있는 프레임에서,

API References > Native Application > Native API Reference > System > dlog

위의 항목을 선택합니다.



플랫폼에서 지원하는 로그를 위한 함수는 2가지가 있습니다.

dlog_print()와 dlog_vprint()입니다.

위의 두 함수는 공통적으로 priority와 tag를 입력해주어야 합니다.


priority는 출력할 로그의 종류에 따라 선택하면 되죠.

개발자의 필요에 따라 사용하는 로그이므로,

priority 사용에 제한을 두진 않습니다.

(소스 출처 : git://review.tizen.org/framework/system/dlog, tizen_2.3)


 * @brief log priority values, in ascending priority order.
 * @since_tizen 2.3
 */
typedef enum {
    DLOG_UNKNOWN = 0, /**< Keep this always at the start */
    DLOG_DEFAULT, /**< Default */
    DLOG_VERBOSE, /**< Verbose */
    DLOG_DEBUG, /**< Debug */
    DLOG_INFO, /**< Info */
    DLOG_WARN, /**< Warning */
    DLOG_ERROR, /**< Error */
    DLOG_FATAL, /**< Fatal */
    DLOG_SILENT, /**< Silent */
    DLOG_PRIO_MAX   /**< Keep this always at the end. */
} log_priority;


tag는 일반적으로 자신의 모듈명을 입력합니다.

플랫폼에서 사용하는 tag는 대부분 대문자와 '_'(underscore)를 사용하고 있네요.


#define LOG_TAG "BUNDLE"

#define LOG_TAG "PKGMGR"

#define LOG_TAG "CAPI_APPFW_APPLICATION"

#define LOG_TAG "VOLUME"


하지만, 소문자만으로 태그이름을 정한 모듈도 있긴 합니다.

이를 보건대 태그이름에 대한 정책적인 제약은 없는 것으로 보이네요.

모듈로그는 태그명으로 검색할 수 있으므로 차후를 위해 잘 기억해두세요.


/**
 * @addtogroup CAPI_SYSTEM_DLOG
 * @{
 */
/**
 * @brief     Send log with priority and tag.
 * @details   for application
 * @since_tizen 2.3
 * @param[in] prio log_priority
 * @param[in] tag tag
 * @param[in] fmt format string
 * @return On success, the function returns the number of bytes written.
 *         On error, a negative errno-style error code
 * @retval #DLOG_ERROR_INVALID_PARAMETER Invalid parameter
 * @retval #DLOG_ERROR_NOT_PERMITTED Operation not permitted
 * @pre       none
 * @post      none
 * @see       dlog_vprint
 * @code
#include<dlog.h>
int main(void)
{
    int integer = 21;
    char string[] = "test dlog";

    dlog_print(DLOG_INFO, "USR_TAG", "test dlog");
    dlog_print(DLOG_INFO, "USR_TAG", "%s, %d", string, integer);
    return 0;
}
 * @endcode
 */
int dlog_print(log_priority prio, const char *tag, const char *fmt, ...);
/**
 * @brief     Send log with priority, tag and va_list.
 * @details   for application
 * @since_tizen 2.3
 * @param[in] prio log_priority
 * @param[in] tag tag
 * @param[in] fmt format string
 * @param[in] ap va_list
 * @return On success, the function returns the number of bytes written.
 *         On error, a negative errno-style error code
 * @retval #DLOG_ERROR_INVALID_PARAMETER Invalid parameter
 * @retval #DLOG_ERROR_NOT_PERMITTED Operation not permitted
 * @pre       none
 * @post      none
 * @see       dlog_print
 * @code
#include<dlog.h>
void my_debug_print(char *format, ...)
{
    va_list ap;

    va_start(ap, format);
    dlog_vprint(DLOG_INFO, "USR_TAG", format, ap);
    va_end(ap);
}

int main(void)
{
    my_debug_print("%s", "test dlog");
    my_debug_print("%s, %d", "test dlog", 21);
    return 0;
}
 * @endcode
 */
int dlog_vprint(log_priority prio, const char *tag, const char *fmt, va_list ap);


함수 정의를 살펴보면,

priority와 tag를 지정한 뒤,

로그로 출력하고자 하는 포맷을 넣어주면 됩니다.

두 함수 모두 내부적으로 동일한 루틴을 따르네요.


static int __write_to_log_sd_journal(log_id_t log_id, log_priority prio, const char *tag, const char *msg)
{
    pid_t tid = (pid_t)syscall(SYS_gettid);
    /* XXX: sd_journal_sendv() with manually filed iov-s might be faster */
    return sd_journal_send("MESSAGE=%s", msg,
                   "PRIORITY=%i", dlog_pri_to_journal_pri(prio),
                   "LOG_TAG=%s", tag,
                   "LOG_ID=%s", dlog_id_to_string(log_id),
                   "TID=%d", tid,
                   NULL);
}


dlog는 내부적으로 systemd의 journal을 사용합니다.

sd-journal(systemd's journal)은 systemd에서 관리하는 로그를 위한 서비스입니다.

dlog는 sd_journal_send()를 사용하여 sd-journal에 로그를 입력하고 있습니다.

sd-journal에 대한 이야기는 기회가 있을때 다시 하기로 하죠.


앱에서는 로그를 수도 없이 사용하므로,

간단히 사용할 수 있는 매크로 함수를 사용하기로 합니다.

실제 타이젠의 시스템 앱에서 사용하고 있는 매크로 함수가 있습니다.

시스템 앱에서 사용하고 있는 매크로 함수를,

SDK로 개발하는 앱에서 사용할 수 있도록 수정하였습니다.

(소스출처 : http://www.freedesktop.org/software/systemd/man/sd-journal.html, master)


#ifndef __YOUR_APPLICATION_LOG_H__
#define __YOUR_APPLICATION_LOG_H__

#include <dlog.h>

#undef LOG_TAG
#define LOG_TAG "YOUR_APPLICATION"

#define COLOR_RED "\033[0;40;31m"
#define COLOR_LIGHTBLUE "\033[1;40;34m"
#define COLOR_YELLOW "\033[1;40;33m"
#define COLOR_END "\033[0;m"
#define COLOR_GRAY "\033[1;40;30m"
#define COLOR_MAGENTA "\033[1;35m"

#if !defined(_D)
#define _D(fmt, arg...) dlog_print(DLOG_DEBUG, LOG_TAG, COLOR_YELLOW fmt COLOR_END"\n", ##arg)
#endif

#if !defined(_W)
#define _W(fmt, arg...) dlog_print(DLOG_WARN, LOG_TAG, COLOR_MAGENTA fmt COLOR_END"\n", ##arg)
#endif

#if !defined(_E)
#define _E(fmt, arg...) dlog_print(DLOG_ERROR, LOG_TAG, COLOR_RED fmt COLOR_END"\n", ##arg)
#endif

#define retv_if(expr, val) do { \
    if(expr) { \
        _E("(%s) -> %s() return", #expr, __FUNCTION__); \
        return (val); \
    } \
} while (0)

#define ret_if(expr) do { \
    if(expr) { \
        _E("(%s) -> %s() return", #expr, __FUNCTION__); \
        return; \
    } \
} while (0)

#define goto_if(expr, val) do { \
    if(expr) { \
        _E("(%s) -> goto", #expr); \
        goto val; \
    } \
} while (0)

#define break_if(expr) { \
    if(expr) { \
        _E("(%s) -> break", #expr); \
        break; \
    } \
}

#define continue_if(expr) { \
    if(expr) { \
        _E("(%s) -> continue", #expr); \
        continue; \
    } \
}

#endif                /* __YOUR_APPLICATION_LOG_H__ */


헤더를 사용하려면 아래 로그헤더를 다운로드 받으시면 됩니다.

log.h

헤더를 사용하시려면,

- LOG_TAG에 자신의 모듈이름을 적어주세요.

- 일반적인 디버그 상황에서 사용하는 디버그 로그는 _D 매크로 함수를 사용하고,

    _D("%s's GEOMETRY : [%d, %d, %d, %d]", (const char *) data, x, y, w, h);

- 에러는 아니지만, 일반적인 디버그 로그보다 위험한 경우는 워닝 로그 _W 매크로 함수를 사용하고,

    _W("cannot find index.");

- 에러 상황에서는 _E 매크로 함수를 사용하시면 됩니다.

    _E("Failed to remove vconf %s", "memory/menuscreen/mapbuf");


로그를 위한 장치가 준비되었으니,

이제 본격적으로 코딩을 하며 '신나게' 로그를 삽입하면 됩니다.

SDK > 하단 프레임 > Log

위의 메뉴와 친해지도록 하세요. :)


끝_

+ Recent posts