This analysis is based on Android S(12)

The ancients were very particular about naming, not only famous, but also characters. Literati sometimes gave themselves another name. The so-called “name proper style, word to express virtue, word to cherish”, the three together to show a person’s character and pursuit. In the Android world, the process and thread name is also varied, some places use “name”, use “word”, not uniform. So the purpose of this article is to get down to the nitty-gritty and let the audience know who the subject is without the code names. Let’s start with a few quick questions:

  1. What is the name of the main thread of an Android application in the trace file? What is the name in the tombstone file?
  2. What is the default name of a new thread created by the Java layer? What is the default name of the new thread created by Native layer?
  3. What is the original meaning of “Cmd line” at the top of the Trace file? Why is it the same as the package name of the application?
  4. What does the CMD column display in pS-A (or ps-E)? Why are some names enclosed by “[]”?

When we study things, we should think in terms of historical change. Therefore, to understand Android, you must first understand Linux.

1. Process and thread names in Linux

There is an important concept in the Linux kernel: task_struct. I understand it as a scheduling entity, the basic unit that participates in scheduling. From an execution perspective, a thread is a scheduling entity. From a memory perspective, the concept of multiple threads forming processes that share memory in user space with each other (of course, threads share more than just memory with each other).

When we need to start a new program, we first get a new running entity via fork or clone. The program is then started by exec in the new run entity. Exec has many variations. Let’s take the common Excel as an example.

int execl(const char *pathname, const char *arg, ... /*, (char *) NULL */);
Copy the code

1.1 the Command Line

The first argument to this function is the pathname of the executable file, and the following arguments together form the Command line. As the kernel processes these command line arguments, it concatenates them sequentially at the bottom of the stack, separating each argument with a ‘\0’. These parameters are passed to the main method (argv[]) when the program starts. By convention (and not by force), the first argument (the string argv[0] points to) is the filename, although you can pass in any other string if you like.

For example, the resulting command line is “banana\0-l\0”, which corresponds to the ASCII code 0x00.

execl("/bin/ls"."banana"."-l".NULL);
Copy the code

Because the strings in the command line are separated by ‘\0’, if we simply print through printf, we will see only the string pointed to by argv[0]. If you want to get everything in the command line completely, you usually need some special handling. Get_command_line retrieves all of the strings, while get_process_name retrieves only the string pointed to by argv[0], which is usually the name of the executable file or the process name for a pure native process. But for Android apps, it means something else. Press the table here, and we’ll talk about it later.

[/system/core/debuggerd/util.cpp]

std::vector<std::string> get_command_line(pid_t pid) {
  std::vector<std::string> result;

  std::string cmdline;
  android::base::ReadFileToString(android::base::StringPrintf("/proc/%d/cmdline", pid), &cmdline);

  auto it = cmdline.cbegin(a);while(it ! = cmdline.cend()) {
    // string::iterator is a wrapped type, not a raw char*.
    auto terminator = std::find(it, cmdline.cend(), '\ 0');
    result.emplace_back(it, terminator);
    it = std::find_if(terminator, cmdline.cend(), [] (char c) { returnc ! ='\ 0'; });
  }
  if (result.empty()) {
    result.emplace_back("<unknown>");
  }

  return result;
}

std::string get_process_name(pid_t pid) {
  std::string result = "<unknown>";
  android::base::ReadFileToString(android::base::StringPrintf("/proc/%d/cmdline", pid), &result);
  // We only want the name, not the whole command line, so truncate at the first NUL.
  return result.c_str(a); }Copy the code

As mentioned earlier, the Command line is connected sequentially to the bottom of the stack, in user space. When we execute the cat /proc/[pid]/cmdline directive in adb shell, we are essentially accessing a special file node (which is only readable). This access action finally triggers a function in kernel space, as follows.

[/kernel/common/fs/proc/base.c]

REG("cmdline",    S_IRUGO, proc_pid_cmdline_ops),
Copy the code

[/kernel//common/fs/proc/base.c]

static const struct file_operations proc_pid_cmdline_ops = {
	.read	= proc_pid_cmdline_read,
	.llseek	= generic_file_llseek,
};
Copy the code

The proc_pid_cmdline_read function accesses the address space of the [PID] process via access_remote_VM to retrieve its command line data stored in user space and copy it to the output buffer. Therefore, command line data does not exist in the kernel address space.

1.2 the Command Name

Each scheduling entity has its own name, which is the “comm” field in task_struct. Comm is a command name, not a command line.

[/kernel/common/include/linux/sched.h]

/* Task command name length: */
#define TASK_COMM_LEN			16.struct task_struct {./* * executable name, excluding path. * * - normally initialized setup_new_exec() * - access it with [gs]et_task_comm() * - lock it with task_lock() */
  charcomm[TASK_COMM_LEN]; . }Copy the code

What exactly does the COMM string store? Only the source code is the most clear. When we call exec to execute the executable, it calls load_elf_binary in the kernel layer, where the task_strcut.com field is set.

[/kernel/common/fs/exec.c]

__set_task_comm(me, kbasename(bprm->filename), true);
Copy the code

[/kernel/common/include/linux/string.h]

/** * kbasename - return the last part of a pathname. * * @path: path to extract the filename from. */
static inline const char *kbasename(const char *path)
{
	const char *tail = strrchr(path, '/');
	return tail ? tail + 1 : path;
}
Copy the code

Exec incoming to be the first parameter on the file name, but it is a path file name, such as/system/bin/surfaceflinger, and deposited in the comm field is stripped path file name, the name of the namely surfaceflinger. Also note that comm length is 16 and any filename that is too long will be truncated. Thus, comm originally meant the name of an executable file, but as systems have evolved, its meaning has evolved beyond that.

2. Process name and thread name in the Ps view

Ps was originally an instruction in the Linux shell to show some information about a process. Android, however, uses the ToyBox implementation, which is slightly different from the native PS approach. Source is located in the external/toybox/toys/posix/ps. C.

Toybox combines the most common Linux command line utilities together into a single BSD-licensed executable that’s simple, small, fast, reasonably standards-compliant, and powerful enough to turn Android into a development environment. See the links on the left for details.

2.1 Ps -a Process name displayed

Ps-a and ps-E perform the same action, showing all processes.

-A  All
-e  Synonym for -A
Copy the code

Here is A sample output of PS-A.

# ps -A
USER           PID  PPID     VSZ    RSS WCHAN            ADDR S NAME
root             1     0 13001184 14608 do_epoll_+          0 S init
root             2     0       0      0 kthreadd            0 S [kthreadd]
root             3     2       0      0 rescuer_t+          0 I [rcu_gp]
...
logd           278     1 13036024  7516 __do_sys_+          0 S logd
lmkd           279     1 13060480  7372 do_epoll_+          0 S lmkd
system        1383     1 13504456 60264 do_epoll_+          0 S surfaceflinger
...
u0_a150       5280  1105 16943368 103628 do_freeze+         0 S com.android.mms
u0_a190       5334  1105 16966004 134128 do_freeze+         0 S com.android.permissioncontroller
u0_a37        5352  1105 16778080 100784 do_freeze+         0 S com.android.providers.calendar
Copy the code

Notice the last column: NAME, which means something like this: Process NAME. But if you look at the output above, you’ll notice a few odd points.

  1. Why are some process names executable files and others application package names?
  2. Why are some process names enclosed in square brackets?

What TM is the process name of TM.

[/external/toybox/toys/posix/ps.c]

// String fields (-1 is procpid->str, rest are str+offset[1-slot])
{"TTY"."Controlling terminal".- 8 -.2 -},
{"WCHAN"."Wait location in kernel".- 6.- 3},
{"LABEL"."Security label".- 30.4 -},
{"COMM"."EXE filename (/proc/PID/exe)".27 -.- 5},
{"NAME"."Process name (PID's argv[0])".27 -.7 -},
{"COMMAND"."EXE path (/proc/PID/exe)".27 -.- 5},
{"CMDLINE"."Command line (argv[])".27 -.- 6},
{"ARGS"."CMDLINE minus initial path".27 -.- 6},
{"CMD"."Thread name (/proc/TID/stat:2)".- 15.- 1},
Copy the code

Process name, as commented above, can be understood as the string pointed to by argv[0]. This data is read from the /proc/[pid]/cmdline file node, but requires some special processing.

[/external/toybox/toys/posix/ps.c]

struct {
  char *name;     // Path under /proc/$PID directory
  long long bits; // Only fetch extra data if an -o field is displaying it
} fetch[] = {
  // sources for procpid->offset[] data
  {"fd/", _PS_TTY}, {"wchan", _PS_WCHAN}, {"attr/current", _PS_LABEL},
  {"exe", _PS_COMMAND|_PS_COMM}, {"cmdline", _PS_CMDLINE|_PS_ARGS|_PS_NAME},
  {"", _PS_NAME}
};
Copy the code

The raw information read from the cmdline file node contains all the parameters, separated by ‘\0’. The Ps process takes the data and does the following:

(Suppose we get the original message: “/system/bin/top\0-d\04\0”)

  1. Replace all ‘\0’ with Spaces, and the processed string can be displayed inCMDLINEThe column. (“/system/bin/top -d 4”)
  2. Replace the first string before ‘\0’ as argv[0] and remove the information before the last ‘/’ of argv[0], leaving only the basic file name (basename). The processed string can be displayed in theNAMEThe column. (Changed to “TOP” after processing)
  3. The path information in argv[0] is removed, but the subsequent parameter information is retained. The processed string can be displayed in theARGSThe column. (Changed to “top-d 4” after processing)

So we can see that the information read out from the cmdline file node ends up being used in three places, a bit like eating three fish with one fish.

The process name that is eventually displayed in ps-a is basename of the argv[0] string, usually the name of the executable file. The Android application is shown as a package name because argv[0] was overwritten during process startup, which will be discussed later.

So why are some process names enclosed in square brackets?

The answer is that these processes do not have CMDline (kernel processes and some special user processes). When the cmdline file node does not read any information, ps will fetch the task_struct.com value of the process and surround it with square brackets instead of displaying it. For these processes, the CMDLINE, NAME, and ARGS columns all display the same string. So when we see a process name like this, we can probably assume that this is a kernel process.

2.2 Top Process name displayed

Top displays the process name ARGS (see #2.1 for details). Strictly speaking, it should not be called “process name”, but “parameter list”. Here we use the top process example (line 2). You can see that ARGS is “top -D 4”, which contains subsequent parameter information.

2.3 Ps-t -p Specifies the thread name

# ps -T -p 5280 USER PID TID PPID VSZ RSS WCHAN ADDR S CMD u0_a150 5280 5280 1105 16943368 103628 do_freeze+ 0 S com.android.mms u0_a150 5280 5281 1105 16943368 103628 do_freeze+ 0 S Runtime worker u0_a150 5280 5282 1105 16943368 103628 do_freeze+ 0 S Runtime worker u0_a150 5280 5283 1105 16943368 103628 do_freeze+ 0 S Runtime worker u0_a150 5280 5284 1105 16943368 103628 do_freeze+ 0 S Runtime worker u0_a150 5280 5285 1105 16943368 103628 do_freeze+ 0 S Signal Catcher u0_a150 5280 5286 1105 16943368 103628 do_freeze+ 0 S perfetto_hprof_ u0_a150 5280 5287 1105 16943368 103628 do_freeze+ 0 S ADB-JDWP Connec u0_a150 5280 5288 1105 16943368 103628 do_freeze+ 0 S Jit thread pool u0_a150 5280 5289 1105 16943368 103628 do_freeze+ 0 S HeapTaskDaemon u0_a150 5280 5290 1105 16943368 103628 do_freeze+ 0 S ReferenceQueueD  u0_a150 5280 5291 1105 16943368 103628 do_freeze+ 0 S FinalizerDaemon u0_a150 5280 5292 1105 16943368 103628 do_freeze+  0 S FinalizerWatchd u0_a150 5280 5293 1105 16943368 103628 do_freeze+ 0 S Binder:5280_1 u0_a150 5280 5294 1105 16943368  103628 do_freeze+ 0 S Binder:5280_2 u0_a150 5280 5295 1105 16943368 103628 do_freeze+ 0 S Binder:5280_3 u0_a150 5280 5303 1105 16943368 103628 do_freeze+ 0 S k worker thread u0_a150 5280 5307 1105 16943368 103628 do_freeze+ 0 S Binder:5280_4 u0_a150 5280 5310 1105 16943368 103628 do_freeze+ 0 S queued-work-loo u0_a150 5280 5312 1105 16943368 103628 do_freeze+ 0 S ent.InfoHandler u0_a150 5280 5313 1105 16943368 103628 do_freeze+ 0 S nt.EventHandler u0_a150 5280  6312 1105 16943368 103628 do_freeze+ 0 S android.bgCopy the code

Ps-t-p will display all threads under a particular process. The display here is called CMD, and this information is obtained by accessing the /proc/ti/stat :2 node information, which is essentially the task_struct.com field with a 16-bit length limit.

(2) comm  %s
       The filename of the executable, in parentheses.
       Strings longer than TASK_COMM_LEN (16) characters
       (including the terminating null byte) are silently
       truncated.  This is visible whether or not the
       executable is swapped out.
Copy the code

If you look closely at the CMD information above, you will notice a strange phenomenon: some truncations are the second half of the reserved name (such as “nt.eventhandler”), while some truncations are the first half of the reserved name (such as “ReferenceQueueD”). We’ll save this for the Android app section.

3. Name of a thread from the Pthread perspective

Native layer thread creation generally adopts PThreads. Both STD :: Threads in LiBCXX and Threads in Java layer are pThreads at the bottom. So to understand exactly what thread names are in your application, you must pass the pthread hurdle.

For pthreads, the thread name is the task_struct.com field.

When we create a thread with pthread_create(), it’s worth noting that the function doesn’t have a thread name set internally, so the Clone action copies the comm field of the calling thread to the new thread. That is, the default thread name of the new thread is the same as that of the calling thread. This is why we see multiple threads with the same name in the SurfaceFlinger process.

A thread name can be changed using the pthread_setname_NP function, which eventually modifies the task_struct.com field in kernel space. One caveat here is that the name passed in cannot be longer than 16, otherwise the setting will not work.

[/bionic/libc/bionic/pthread_setname_np.cpp]

int pthread_setname_np(pthread_t t, const char* thread_name) {
  ErrnoRestorer errno_restorer;

  size_t thread_name_len = strlen(thread_name);
  if (thread_name_len >= MAX_TASK_COMM_LEN) return ERANGE;
Copy the code

4. Process and thread names from the Android perspective

4.1 Zygote process name and thread name

The following discussions are for 64-bit Zygote processes

Zygote64. Rc file to start the 64-bit zygote process, which is essentially an exec call after fork, with the following parameters, total length 78 (including the ending ‘\0’). Zygote64_32.rc files are used to start the 64-bit Zygote process on many existing machines. This is a parameter with a total length of 99, including the ending ‘\0’.

/system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
Copy the code

[/frameworks/base/cmds/app_process/app_main.cpp]

#if defined(__LP64__)
static const char ABI_LIST_PROPERTY[] = "ro.product.cpu.abilist64";
static const char ZYGOTE_NICE_NAME[] = "zygote64";
#else.int main(int argc, char* const argv[])
{...AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv)); .while (i < argc) {
        const char* arg = argv[i++];
        if (strcmp(arg, "--zygote") = =0) {
            zygote = true; niceName = ZYGOTE_NICE_NAME; .if(! niceName.isEmpty()) {
        runtime.setArgv0(niceName.string(), true /* setProcName */);
    }
Copy the code

After Exec execution, zygote’s process name and main thread name are both set to app_process64, which is the name of the executable file. But they are modified inside main, through the setArgv0 function.

[/frameworks/base/core/jni/AndroidRuntime.cpp]

void AndroidRuntime::setArgv0(const char* argv0, bool setProcName) {
    // Set the kernel's task name, for as much of the name as we can fit.
    // The kernel's TASK_COMM_LEN minus one for the terminating NUL == 15.
    if (setProcName) {
        int len = strlen(argv0);
        if (len < 15) {
            pthread_setname_np(pthread_self(), argv0);
        } else {
            pthread_setname_np(pthread_self(), argv0 + len - 15); }}// Directly change the memory pointed to by argv[0].
    memset(mArgBlockStart, 0, mArgBlockLength);
    strlcpy(mArgBlockStart, argv0, mArgBlockLength);

    // Let bionic know that we just did that, because __progname points
    // into argv[0] (https://issuetracker.google.com/152893281).
    setprogname(mArgBlockStart);
}
Copy the code

The SetArgv0 function does three things:

  1. Change the name of the Zygote main thread to “zygote64”, i.etask_struct.commField.
  2. Change the command line at the bottom of the stack to “zygote64”. At this time visit/proc/[zygote's pid]/cmdlineFile node, get only “zygote64”. So whether we use the full string of cmdline, the string pointed to by argv[0], or the basename stripped from the path by argv[0], we will get “zygote64”. Therefore, it stands to reason that the process name has been changed to “zygote64” at this point.
  3. make__prognamePoints to the beginning of the command line, which is mainly used in LIBC.

After setArgv0 is executed, the zygote process name and main thread name are changed to “Zygote64”. But is that the end of the matter? Will not!

Zygote then starts the virtual machine and performs the following functions at the end of the virtual machine startup.

[/art/runtime/thread.cc]

void Thread::FinishStartup(a) {
  Runtime* runtime = Runtime::Current(a);CHECK(runtime->IsStarted());

  // Finish attaching the main thread.
  ScopedObjectAccess soa(Thread::Current());
  soa.Self() - >CreatePeer("main".false, runtime->GetMainThreadGroup());
Copy the code

SetThreadName is called inside CreatePeer to change the name of the thread again.

[/art/runtime/thread.cc]

void Thread::SetThreadName(const char* name) {
  tlsPtr_.name->assign(name);
  ::art::SetThreadName(name);
  Dbg::DdmSendThreadNotification(this.CHUNK_TYPE("THNM"));
}
Copy the code

The thread name has two meanings here, because the main thread after the virtual machine is started is not only a pThread thread, but also an ART thread.

  1. The first meaning:task_struct.commField, the thread name of the pThread, which is stored in the kernel address space.
  2. Each ART Thread corresponds to an ART ::Thread object with an internal field:tlsPtr_.name. The name is stored in the user address space.

Going back to the SetThreadName function, it changes the thread name of both meanings. First change the tlsPtr_. Name field to “main”, and then change the task_struct.com field to “main” with ::art::SetThreadName.

[/art/libartbase/base/utils.cc]

void SetThreadName(const char* thread_name) {
  bool hasAt = false;
  bool hasDot = false;
  const char* s = thread_name;
  while (*s) {
    if (*s == '. ') {
      hasDot = true;
    } else if (*s == The '@') {
      hasAt = true;
    }
    s++;
  }
  int len = s - thread_name;
  if (len < 15|| hasAt || ! hasDot) { s = thread_name; }else {
    s = thread_name + len - 15;
  }
#if defined(__linux__) || defined(_WIN32)
  // pthread_setname_np fails rather than truncating long strings.
  char buf[16];       // MAX_TASK_COMM_LEN=16 is hard-coded in the kernel.
  strncpy(buf, s, sizeof(buf)- 1);
  buf[sizeof(buf)- 1] = '\ 0';
  errno = pthread_setname_np(pthread_self(), buf);
  if(errno ! =0) {
    PLOG(WARNING) << "Unable to set the name of current thread to '" << buf << "'";
  }
#else  // __APPLE__
  pthread_setname_np(thread_name);
#endif
}
Copy the code

::art::SetThreadName There is some special processing for the name passed in as follows.

  1. If the name passed contains the ‘@’ symbol, or does not contain the ‘.’ symbol, the first half is preserved when truncated.
  2. Otherwise the second half of the string is preserved when truncated.

It’s not about knowing the rules, it’s about understanding the thinking behind them. Here’s how I understand this rule: the 16-character limit is a sacrifice that kernel space has to make in order to control the size of task_struct structures, and for names that are longer than 16, Google’s goal is to preserve the most informative parts of them. The first half of a name, which is separated by the ‘.’ symbol, is usually low in information. Take package names for example. The first half is usually “com” or “org”, and the last part is the most unique and informative part of each package name. The ‘@’ sign is usually followed by version information, which is not important for understanding the identity of the thread, because there are rarely more than one version in a system.

Returning to the thread names shown earlier in Ps-t-p, thread 5290 retains the first half and thread 5312 retains the second half. With the rules just introduced, I think you can understand more deeply.

u0_a150 5280 5290 1105 16943368 103628 do_freeze+ 0 S ReferenceQueueD
u0_a150 5280 5291 1105 16943368 103628 do_freeze+ 0 S FinalizerDaemon
u0_a150 5280 5292 1105 16943368 103628 do_freeze+ 0 S FinalizerWatchd
u0_a150 5280 5310 1105 16943368 103628 do_freeze+ 0 S queued-work-loo
u0_a150 5280 5312 1105 16943368 103628 do_freeze+ 0 S ent.InfoHandler
Copy the code

Continue back to the Zygote process. Zygote’s main thread name changes to “main” after the vm is started, both from the pthread perspective (task_struct.com) and from the ART thread perspective (tlsptr_.name).

4.2 Process and thread names of Android Applications

The Android application process is spawned by zygote fork, and this fork occurs on the main thread of zygote. After the fork completes, the application (currently only one thread) has the same task_struct.com as the Zygote main thread and tlsPtr_. Name as the Zygote main thread (“main”).

The application process main thread then calls SpecializeCommon, which changes the thread name again. Nice_name is also the process name declared by the application in the manifest, which is the same as the package name by default unless “Android :process” is set.

[/frameworks/base/core/jni/com_android_internal_os_Zygote.cpp]

// Make it easier to debug audit logs by setting the main thread's name to the
// nice name rather than "app_process".
if (nice_name.has_value()) {
    SetThreadName(nice_name.value());
} else if (is_system_server) {
    SetThreadName("system_server");
}
Copy the code

Note, however, that SetThreadName will only modify task_struct.com, not tlsPtr_. Name. So if we treat this thread as a pthread, its name is the package name; However, if we think of it as an ART thread, its name would be “main”.

[/frameworks/base/core/jni/com_android_internal_os_Zygote.cpp]

void SetThreadName(const std::string& thread_name) {
  bool hasAt = false;
  bool hasDot = false;

  for (const char str_el : thread_name) {
    if (str_el == '. ') {
      hasDot = true;
    } else if (str_el == The '@') {
      hasAt = true; }}const char* name_start_ptr = thread_name.c_str(a);if (thread_name.length() >= MAX_NAME_LENGTH && ! hasAt && hasDot) { name_start_ptr += thread_name.length() - MAX_NAME_LENGTH;
  }

  // pthread_setname_np fails rather than truncating long strings.
  char buf[16];       // MAX_TASK_COMM_LEN=16 is hard-coded into bionic
  strlcpy(buf, name_start_ptr, sizeof(buf) - 1);
  errno = pthread_setname_np(pthread_self(), buf);
  if(errno ! =0) {
    ALOGW("Unable to set the name of current thread to '%s': %s", buf, strerror(errno));
  }
  // Update base::logging default tag.
  android::base::SetDefaultTag(buf);
}
Copy the code

After SpecializeCommon completes, the main thread calls setArgv0 to change the process name, changing the command line from “zygote64” to the application package name.

At this point, the application’s command line and the main thread’s task_struct.com are both set to the package name, while the main thread’s tlsPtr_. Name is still “main”.

4.3 Name of a new Java thread

The Thread we create in Java is essentially an ART Thread, while the Thread object in The Java layer is more like a puppet, and its core operation and data are in the ART ::Thread object in the Native layer. When we create a Thread object in the Java layer, the corresponding art::Thread is not created. The art::Thread is created only when we call thread.start ().

After the art::Thread is successfully created and started, the new Thread changes its name to the name passed in when the Thread was created. If you do not specify a name when creating a pthread, the system will automatically name it as “Thread”+” ordinal “, unlike pthreads.

[/libcore/ojluni/src/main/java/java/lang/Thread.java]

public Thread(ThreadGroup group, Runnable target) {
    init(group, target, "Thread-" + nextThreadNum(), 0);
}
Copy the code

However, even after the Thread is started, we can change the Thread name later by using Thread.setName.

Unlike the main thread, these threads modify both task_struct.com and tlsPtr_. Name when changing the name.

Process and thread names in the 4.4 Trace file and Tombstone file

For most developers, the main place they come into contact with process and thread names is in trace files and tombstone files.

[Trace file example]

----- pid 9000 at 2022-03-17 05:00:52.489353500+0000 -----
Cmd line: com.hangl.helloworldhelloworldhelloworldhelloworldhelloworldhelloworldhellowo

DALVIK THREADS (16):
"Signal Catcher" daemon prio=10 tid=5 Runnable
...
"main" prio=5 tid=1 Native
...
"ReferenceQueueDaemon" daemon prio=5 tid=12 Waiting
Copy the code

The string after “Cmd line” is obtained by accessing the /proc/self-/cmdline file node. It simply removes the superfluous ‘\0’ at the end of the original string and replaces the delimited ‘\0’ with Spaces. After SpecializeCommon completes, the main application will call setArgv0 to change the process name from “zygote64” to the application package name.

[/art/runtime/signal_catcher.cc]

static void DumpCmdLine(std::ostream& os) {
#if defined(__linux__)
  // Show the original command line, and the current command line too if it's changed.
  // On Android, /proc/self/cmdline will have been rewritten to something like "system_server".
  // Note: The string "Cmd line:" is chosen to match the format used by debuggerd.
  std::string current_cmd_line;
  if (android::base::ReadFileToString("/proc/self/cmdline", &current_cmd_line)) {
    current_cmd_line.resize(current_cmd_line.find_last_not_of('\ 0') + 1);  // trim trailing '\0's
    std::replace(current_cmd_line.begin(), current_cmd_line.end(), '\ 0'.' ');

    os << "Cmd line: " << current_cmd_line << "\n";
    const char* stashed_cmd_line = GetCmdLine(a);if(stashed_cmd_line ! =nullptr&& current_cmd_line ! = stashed_cmd_line &&strcmp(stashed_cmd_line, "<unset>") != 0) {
      os << "Original command line: " << stashed_cmd_line << "\n"; }}#else
  os << "Cmd line: " << GetCmdLine() < <"\n";
#endif
}
Copy the code

Continuing, the “Cmd line” shown here is also limited in length. Its maximum length is init.zygote64.rc the length of the argument passed when zygote is started, currently 78(including the ending ‘\0’). I don’t know if you noticed the sample trace file above, but I declared the package name to be over the maximum length. “Cmd line” only keeps the first 77 characters, plus the ending ‘\0’, which is exactly 78. The comparison is as follows. (If you use init.zygote64_32.rc on your machine, 98 characters will be saved.)

Package Name: com.hangl.helloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworld
----- pid 5129 at 2022-03-18 03:23:41 -----
Cmd line: com.hangl.helloworldhelloworldhelloworldhelloworldhelloworldhelloworldhellowo
Copy the code

This is followed by the thread name shown in the trace file. TlsPtr_. Name is displayed instead of task_struct.com. TlsPtr_. Name is “main” and task_struct.com is the package name, so the main program is called “main”. Other threads do not have this divergence.

[/art/runtime/thread.cc]

if(thread ! =nullptr) {
  os << '"' << *thread->tlsPtr_.name << '"';
Copy the code

[Tombstone file example]

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Cmdline: com.hangl.helloworldhelloworldhelloworldhelloworldhelloworldhelloworldhellowo
pid: 9000, tid: 9000, name: worldhelloworld  >>> com.hangl.helloworldhelloworldhelloworldhelloworldhelloworldhelloworldhellowo <<<
...
pid: 9000, tid: 9010, name: ReferenceQueueD  >>> com.hangl.helloworldhelloworldhelloworldhelloworldhelloworldhelloworldhellowo <<<
Copy the code

Cmdline in Tombstone is the same as Trace, replacing the delimited ‘\0’ with Spaces.

[/system/core/debuggerd/util.cpp]

std::vector<std::string> get_command_line(pid_t pid) {
  std::vector<std::string> result;

  std::string cmdline;
  android::base::ReadFileToString(android::base::StringPrintf("/proc/%d/cmdline", pid), &cmdline);

  auto it = cmdline.cbegin(a);while(it ! = cmdline.cend()) {
    // string::iterator is a wrapped type, not a raw char*.
    auto terminator = std::find(it, cmdline.cend(), '\ 0');
    result.emplace_back(it, terminator);
    it = std::find_if(terminator, cmdline.cend(), [] (char c) { returnc ! ='\ 0'; });
  }
  if (result.empty()) {
    result.emplace_back("<unknown>");
  }

  return result;
}
Copy the code

[/system/core/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp]

CB(should_log, "Cmdline: %s", android::base::Join(tombstone.command_line(), "").c_str());
Copy the code

Task_struct.com is used instead of tlsPtr_. Name. This makes sense because tombstone is a mechanism for all user processes and treats threads only as pthreads, not as ART threads. As a pthread, its thread name exists only in tlsPtr_. Name.

As a result, the main application name appears as the truncated package name, truncated because task_struct.com has a 16-bit length limit. And because the package name contains the ‘.’ symbol, the front truncation is used to preserve the second half. In addition, the names of other threads may be truncated, which does not happen in the trace file. For example, for the same “ReferenceQueueDaemon” thread, the name in the trace file is displayed intact, while the name in the tombstone file is truncated.

conclusion

This paper analyzes the different understanding of process/thread names from a bottom-up, hierarchical perspective. It’s a lot of detail, and it can seem confusing, so here’s a summary.

  1. Argv [0] : argv[0] : argv[0] : argv[0] : argv[0] The process name in the Trace file and tombstone file is a complete command line. The application will rewrite the command line to the package name at startup. Any portion exceeding 77 characters will be truncated.
  2. The name of the thread from the Pthread perspectivetask_struct.comm, has a 16-bit length limit; The name of the thread from the ART Thread perspectivetlsPtr_.name, there is no length limit.
  3. The thread created by Pthread_create or STD ::thread. The default thread name is the same as the creator. The default Thread name is “Thread-“+” serial number “.

This article looks like “there are several ways to write anise in anise beans”, but I am not idle to do this article. Some time ago, when I was developing a feature, I needed to set it according to the process name. When I was writing the code, I thought: what is the process name? Is the process name seen at the bottom consistent with the process name seen at the upper layer? After thinking about the problem, I found I didn’t understand it. If you don’t understand it, you have to study it, hence this article.