Android LOG system source code analysis

Android LOG system source code analysis (I)

After studying Lao Luo's "Android system source code scenario analysis", I found that the code version is a little old, which is quite different from the current version, so I combed it again based on android 11.

Old version difference

The logger driver is deleted and the logd is added to implement the logging system. The source code path is system/core/logd

log system startup and initialization

See the logd RC file can think that the logd should be resolved by the init process when the system is started RC file. Check init RC files do.

# system/core/rootdir/init.rc

on init
    # Start logd before any other services run to ensure we capture all of their logs.
    start logd
    # Start lmkd before any other services run so that it can register them

    start lmkd

    # Start essential services.
    start servicemanager
    start hwservicemanager
    start vndservicemanager

Next, let's look at logd What is started in the RC file

# system/core/logd/logd.rc
service logd /system/bin/logd
    socket logd stream 0666 logd logd
    socket logdr seqpacket 0666 logd logd
    socket logdw dgram+passcred 0222 logd logd
    file /proc/kmsg r
    file /dev/kmsg w
    user logd
    group logd system package_info readproc
    capabilities SYSLOG AUDIT_CONTROL
    priority 10
    writepid /dev/cpuset/system-background/tasks

service logd-reinit /system/bin/logd --reinit
    oneshot
    disabled
    user logd
    group logd
    writepid /dev/cpuset/system-background/tasks
    
# Limit SELinux denial generation to 5/second
service logd-auditctl /system/bin/auditctl -r 5
    oneshot
    disabled
    user logd
    group logd
    capabilities AUDIT_CONTROL

From the service, we can see that he did the following things

  1. Start the logd placed under system/bin
  2. Start three socket s, logd, logw and logr
  3. Generate two files /proc/kmsg and /dev/kmsg
  4. Start logd reinit and set oneshot, so it will only be called once
  5. Start logd auditctl

Start logd

Look at the main method. In fact, the comments in the code are also very detailed. Several things were done in the main method

  1. Open the /dev/kmsg file to get fd
  2. According to the system attribute ro logd. The kernel judges that if it is true, open the /proc/kmsg file to get fd
  3. Start the reinit thread, and call when logd reinit passes in the parameter reinit
  4. Create LogBuffer. This object saves all logs, but initialization is not here
  5. Start the listener of each socket
    1. LogReader listens to /dev/socket/logdr and sends logs in LogBuffer when there is a client connection
    2. LogListener listens to /dev/socket/logdw. When a log is written, it tells about data writing, such as LogBuff
    3. CommandListener listens to /dev/socket/logd, and is responsible for listening to and processing the instructions given to logd
  6. If ro logd. When auditd is true, LogAudit will be started, which is mainly responsible for selinux related logs
  7. If ro logd. When the kernel is set to true, LogKlog will be started, which is mainly responsible for collecting kernel related logs

Here is the source code:

int main(int argc, char* argv[]) {
     if ((argc > 1) && argv[1] && !strcmp(argv[1], "--reinit")) {
        return issueReinit();
    }

    static const char dev_kmsg[] = "/dev/kmsg";
    fdDmesg = android_get_control_file(dev_kmsg);
    if (fdDmesg < 0) {
        fdDmesg = TEMP_FAILURE_RETRY(open(dev_kmsg, O_WRONLY | O_CLOEXEC));
    }

    int fdPmesg = -1;
    bool klogd = __android_logger_property_get_bool(
        "ro.logd.kernel",
        BOOL_DEFAULT_TRUE | BOOL_DEFAULT_FLAG_ENG | BOOL_DEFAULT_FLAG_SVELTE);
    if (klogd) {
        static const char proc_kmsg[] = "/proc/kmsg";
        fdPmesg = android_get_control_file(proc_kmsg);
        if (fdPmesg < 0) {
            fdPmesg = TEMP_FAILURE_RETRY(
                open(proc_kmsg, O_RDONLY | O_NDELAY | O_CLOEXEC));
        }
        if (fdPmesg < 0) android::prdebug("Failed to open %s\n", proc_kmsg);
    }

    bool auditd = __android_logger_property_get_bool("ro.logd.auditd", BOOL_DEFAULT_TRUE);
    if (DropPrivs(klogd, auditd) != 0) {
        return EXIT_FAILURE;
    }

    // Reinit Thread
    sem_init(&reinit, 0, 0);
    pthread_attr_t attr;
    if (!pthread_attr_init(&attr)) {
        struct sched_param param;

        memset(&param, 0, sizeof(param));
        pthread_attr_setschedparam(&attr, &param);
        pthread_attr_setschedpolicy(&attr, SCHED_BATCH);
        if (!pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)) {
            pthread_t thread;
            reinit_running = true;
            if (pthread_create(&thread, &attr, reinit_thread_start, nullptr)) {
                reinit_running = false;
            }
        }
        pthread_attr_destroy(&attr);
    }

    // Serves the purpose of managing the last logs times read on a
    // socket connection, and as a reader lock on a range of log
    // entries.

    LastLogTimes* times = new LastLogTimes();

    // LogBuffer is the object which is responsible for holding all
    // log entries.

    logBuf = new LogBuffer(times);

    signal(SIGHUP, reinit_signal_handler);

    if (__android_logger_property_get_bool(
            "logd.statistics", BOOL_DEFAULT_TRUE | BOOL_DEFAULT_FLAG_PERSIST |
                                   BOOL_DEFAULT_FLAG_ENG |
                                   BOOL_DEFAULT_FLAG_SVELTE)) {
        logBuf->enableStatistics();
    }

    // LogReader listens on /dev/socket/logdr. When a client
    // connects, log entries in the LogBuffer are written to the client.

    LogReader* reader = new LogReader(logBuf);
    if (reader->startListener()) {
        return EXIT_FAILURE;
    }

    // LogListener listens on /dev/socket/logdw for client
    // initiated log messages. New log entries are added to LogBuffer
    // and LogReader is notified to send updates to connected clients.

    LogListener* swl = new LogListener(logBuf, reader);
    // Backlog and /proc/sys/net/unix/max_dgram_qlen set to large value
    if (swl->startListener(600)) {
        return EXIT_FAILURE;
    }

    // Command listener listens on /dev/socket/logd for incoming logd
    // administrative commands.

    CommandListener* cl = new CommandListener(logBuf, reader, swl);
    if (cl->startListener()) {
        return EXIT_FAILURE;
    }

    // LogAudit listens on NETLINK_AUDIT socket for selinux
    // initiated log messages. New log entries are added to LogBuffer
    // and LogReader is notified to send updates to connected clients.

    LogAudit* al = nullptr;
    if (auditd) {
        al = new LogAudit(logBuf, reader,
                          __android_logger_property_get_bool(
                              "ro.logd.auditd.dmesg", BOOL_DEFAULT_TRUE)
                              ? fdDmesg
                              : -1);
    }

    LogKlog* kl = nullptr;
    if (klogd) {
        kl = new LogKlog(logBuf, reader, fdDmesg, fdPmesg, al != nullptr);
    }

    readDmesg(al, kl);
    
    if (kl && kl->startListener()) {
        delete kl;
    }

    if (al && al->startListener()) {
        delete al;
    }

    TEMP_FAILURE_RETRY(pause());

    return EXIT_SUCCESS;
}

Initialize LogBuff

When starting logd, the reinit thread pthread is started_ Create (&thread, &attr, reinit_thread_start, nullptr) and wait for reinit's signal to execute in logd RC knows that logd reinit will be started after the start of logd, which is also the main method of logd. At this time, the - reinit parameter will be passed in to call issueReinit, and the reinit instruction will be sent to make reinit_ thread_ Start and continue to initialize LogBuff.

static void* reinit_thread_start(void* /*obj*/) {
    prctl(PR_SET_NAME, "logd.daemon");
    while (reinit_running && !sem_wait(&reinit) && reinit_running) {
        if (fdDmesg >= 0) {
            static const char reinit_message[] = { KMSG_PRIORITY(LOG_INFO),
                                                   'l',
                                                   'o',
                                                   'g',
                                                   'd',
                                                   '.',
                                                   'd',
                                                   'a',
                                                   'e',
                                                   'm',
                                                   'o',
                                                   'n',
                                                   ':',
                                                   ' ',
                                                   'r',
                                                   'e',
                                                   'i',
                                                   'n',
                                                   'i',
                                                   't',
                                                   '\n' };
            write(fdDmesg, reinit_message, sizeof(reinit_message));
        }

        // Anything that reads persist.<property>
        if (logBuf) {
            logBuf->init();
            logBuf->initPrune(nullptr);
        }
        android::ReReadEventLogTags();
    }

    return nullptr;
}

Start logd auditctl

The function of logd auditctl is mainly to limit the selinux log writing frequency to 5 seconds, which is rarely used in daily development. The relevant services are started here.

Listening start

Here's a little more about the methods of monitoring startup and acceptance. In the code, the startListener() is called for listening and starting. This method comes from the SocketListener. LogReader and LogListener both inherit the SocketListener.

Method's call chain is

startListener()

​ ->threadStart(void *obj)

​ ->runListener()

# system/core/libsysutils/src/SocketListener.cpp

int SocketListener::startListener(int backlog) {
    if (mListen && listen(mSock, backlog) < 0) {
        SLOGE("Unable to listen on socket (%s)", strerror(errno));
        return -1;
    } else if (!mListen)
        mClients[mSock] = new SocketClient(mSock, false, mUseCmdNum);

    if (pipe2(mCtrlPipe, O_CLOEXEC)) {
        SLOGE("pipe failed (%s)", strerror(errno));
        return -1;
    }

    if (pthread_create(&mThread, nullptr, SocketListener::threadStart, this)) {
        SLOGE("pthread_create (%s)", strerror(errno));
        return -1;
    }

    return 0;
}
void SocketListener::runListener() {
    while (true) {
 
        for (SocketClient* c : pending) {
            // Process it, if false is returned, remove from the map
            SLOGV("processing fd %d", c->getSocket());
            if (!onDataAvailable(c)) {
                release(c, false);
            }
            c->decRef();
        }
    }
}

The code is quite long. It only shows the core code. The main functions are two

  1. Get socket by name and create socketclient
  2. Start the thread, use the while(true) loop to obtain data in the thread, and process the data through onDataAvailable(). Subclasses implement their respective functions by overriding onDataAvailable, so when you see the processing of LogReader and LogListener, you can directly see the implementation in onDataAvailable.

Tags: Android

Posted by rbblue8 on Wed, 01 Jun 2022 22:19:20 +0530