Proc.cpp 8.64 KB
Newer Older
Abhijith PA's avatar
Abhijith PA committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
/**
 * Copyright (C) ARM Limited 2013-2014. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include "Proc.h"

#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "Buffer.h"
#include "DynBuf.h"
#include "Logging.h"
#include "SessionData.h"

struct ProcStat {
	// From linux-dev/include/linux/sched.h
#define TASK_COMM_LEN 16
	// TASK_COMM_LEN may grow, so be ready for it to get larger
	char comm[2*TASK_COMM_LEN];
	long numThreads;
};

static bool readProcStat(ProcStat *const ps, const char *const pathname, DynBuf *const b) {
	if (!b->read(pathname)) {
		logg->logMessage("%s(%s:%i): DynBuf::read failed, likely because the thread exited", __FUNCTION__, __FILE__, __LINE__);
		// This is not a fatal error - the thread just doesn't exist any more
		return true;
	}

	char *comm = strchr(b->getBuf(), '(');
	if (comm == NULL) {
		logg->logMessage("%s(%s:%i): parsing stat failed", __FUNCTION__, __FILE__, __LINE__);
		return false;
	}
	++comm;
	char *const str = strrchr(comm, ')');
	if (str == NULL) {
		logg->logMessage("%s(%s:%i): parsing stat failed", __FUNCTION__, __FILE__, __LINE__);
		return false;
	}
	*str = '\0';
	strncpy(ps->comm, comm, sizeof(ps->comm) - 1);
	ps->comm[sizeof(ps->comm) - 1] = '\0';

	const int count = sscanf(str + 2, " %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %ld", &ps->numThreads);
	if (count != 1) {
		logg->logMessage("%s(%s:%i): sscanf failed", __FUNCTION__, __FILE__, __LINE__);
		return false;
	}

	return true;
}

static const char APP_PROCESS[] = "app_process";

static const char *readProcExe(DynBuf *const printb, const int pid, const int tid, DynBuf *const b) {
	if (tid == -1 ? !printb->printf("/proc/%i/exe", pid)
			: !printb->printf("/proc/%i/task/%i/exe", pid, tid)) {
		logg->logMessage("%s(%s:%i): DynBuf::printf failed", __FUNCTION__, __FILE__, __LINE__);
		return NULL;
	}

	const int err = b->readlink(printb->getBuf());
	const char *image;
	if (err == 0) {
		image = strrchr(b->getBuf(), '/');
		if (image == NULL) {
			image = b->getBuf();
		} else {
			++image;
		}
	} else if (err == -ENOENT) {
		// readlink /proc/[pid]/exe returns ENOENT for kernel threads
		image = "\0";
	} else {
		logg->logMessage("%s(%s:%i): DynBuf::readlink failed", __FUNCTION__, __FILE__, __LINE__);
		return NULL;
	}

	// Android apps are run by app_process but the cmdline is changed to reference the actual app name
	// On 64-bit android app_process can be app_process32 or app_process64
	if (strncmp(image, APP_PROCESS, sizeof(APP_PROCESS) - 1) != 0) {
		return image;
	}

	if (tid == -1 ? !printb->printf("/proc/%i/cmdline", pid)
			: !printb->printf("/proc/%i/task/%i/cmdline", pid, tid)) {
		logg->logMessage("%s(%s:%i): DynBuf::printf failed", __FUNCTION__, __FILE__, __LINE__);
		return NULL;
	}

	if (!b->read(printb->getBuf())) {
		logg->logMessage("%s(%s:%i): DynBuf::read failed, likely because the thread exited", __FUNCTION__, __FILE__, __LINE__);
		return NULL;
	}

	return b->getBuf();
}

static bool readProcTask(const uint64_t currTime, Buffer *const buffer, const int pid, DynBuf *const printb, DynBuf *const b1, DynBuf *const b2) {
	bool result = false;

	if (!b1->printf("/proc/%i/task", pid)) {
		logg->logMessage("%s(%s:%i): DynBuf::printf failed", __FUNCTION__, __FILE__, __LINE__);
		return result;
	}
	DIR *task = opendir(b1->getBuf());
	if (task == NULL) {
		logg->logMessage("%s(%s:%i): opendir failed", __FUNCTION__, __FILE__, __LINE__);
		// This is not a fatal error - the thread just doesn't exist any more
		return true;
	}

	struct dirent *dirent;
	while ((dirent = readdir(task)) != NULL) {
		char *endptr;
		const int tid = strtol(dirent->d_name, &endptr, 10);
		if (*endptr != '\0') {
			// Ignore task items that are not integers like ., etc...
			continue;
		}

		if (!printb->printf("/proc/%i/task/%i/stat", pid, tid)) {
			logg->logMessage("%s(%s:%i): DynBuf::printf failed", __FUNCTION__, __FILE__, __LINE__);
			goto fail;
		}
		ProcStat ps;
		if (!readProcStat(&ps, printb->getBuf(), b1)) {
			logg->logMessage("%s(%s:%i): readProcStat failed", __FUNCTION__, __FILE__, __LINE__);
			goto fail;
		}

		const char *const image = readProcExe(printb, pid, tid, b2);
		if (image == NULL) {
			logg->logMessage("%s(%s:%i): readImage failed", __FUNCTION__, __FILE__, __LINE__);
			goto fail;
		}

		buffer->comm(currTime, pid, tid, image, ps.comm);
	}

	result = true;

 fail:
	closedir(task);

	return result;
}

bool readProcComms(const uint64_t currTime, Buffer *const buffer, DynBuf *const printb, DynBuf *const b1, DynBuf *const b2) {
	bool result = false;

	DIR *proc = opendir("/proc");
	if (proc == NULL) {
		logg->logMessage("%s(%s:%i): opendir failed", __FUNCTION__, __FILE__, __LINE__);
		return result;
	}

	struct dirent *dirent;
	while ((dirent = readdir(proc)) != NULL) {
		char *endptr;
		const int pid = strtol(dirent->d_name, &endptr, 10);
		if (*endptr != '\0') {
			// Ignore proc items that are not integers like ., cpuinfo, etc...
			continue;
		}

		if (!printb->printf("/proc/%i/stat", pid)) {
			logg->logMessage("%s(%s:%i): DynBuf::printf failed", __FUNCTION__, __FILE__, __LINE__);
			goto fail;
		}
		ProcStat ps;
		if (!readProcStat(&ps, printb->getBuf(), b1)) {
			logg->logMessage("%s(%s:%i): readProcStat failed", __FUNCTION__, __FILE__, __LINE__);
			goto fail;
		}

		if (ps.numThreads <= 1) {
			const char *const image = readProcExe(printb, pid, -1, b1);
			if (image == NULL) {
				logg->logMessage("%s(%s:%i): readImage failed", __FUNCTION__, __FILE__, __LINE__);
				goto fail;
			}

			buffer->comm(currTime, pid, pid, image, ps.comm);
		} else {
			if (!readProcTask(currTime, buffer, pid, printb, b1, b2)) {
				logg->logMessage("%s(%s:%i): readProcTask failed", __FUNCTION__, __FILE__, __LINE__);
				goto fail;
			}
		}
	}

	result = true;

 fail:
	closedir(proc);

	return result;
}

bool readProcMaps(const uint64_t currTime, Buffer *const buffer, DynBuf *const printb, DynBuf *const b) {
	bool result = false;

	DIR *proc = opendir("/proc");
	if (proc == NULL) {
		logg->logMessage("%s(%s:%i): opendir failed", __FUNCTION__, __FILE__, __LINE__);
		return result;
	}

	struct dirent *dirent;
	while ((dirent = readdir(proc)) != NULL) {
		char *endptr;
		const int pid = strtol(dirent->d_name, &endptr, 10);
		if (*endptr != '\0') {
			// Ignore proc items that are not integers like ., cpuinfo, etc...
			continue;
		}

		if (!printb->printf("/proc/%i/maps", pid)) {
			logg->logMessage("%s(%s:%i): DynBuf::printf failed", __FUNCTION__, __FILE__, __LINE__);
			goto fail;
		}
		if (!b->read(printb->getBuf())) {
			logg->logMessage("%s(%s:%i): DynBuf::read failed, likely because the process exited", __FUNCTION__, __FILE__, __LINE__);
			// This is not a fatal error - the process just doesn't exist any more
			continue;
		}

		buffer->maps(currTime, pid, pid, b->getBuf());
	}

	result = true;

 fail:
	closedir(proc);

	return result;
}

bool readKallsyms(const uint64_t currTime, Buffer *const buffer, const bool *const isDone) {
	int fd = ::open("/proc/kallsyms", O_RDONLY | O_CLOEXEC);

	if (fd < 0) {
		logg->logMessage("%s(%s:%i): open failed", __FUNCTION__, __FILE__, __LINE__);
		return true;
	};

	char buf[1<<12];
	ssize_t pos = 0;
	while (gSessionData->mSessionIsActive && !ACCESS_ONCE(*isDone)) {
		// Assert there is still space in the buffer
		if (sizeof(buf) - pos - 1 == 0) {
			logg->logError(__FILE__, __LINE__, "no space left in buffer");
			handleException();
		}

		{
			// -1 to reserve space for \0
			const ssize_t bytes = ::read(fd, buf + pos, sizeof(buf) - pos - 1);
			if (bytes < 0) {
				logg->logError(__FILE__, __LINE__, "read failed", __FUNCTION__, __FILE__, __LINE__);
				handleException();
			}
			if (bytes == 0) {
				// Assert the buffer is empty
				if (pos != 0) {
					logg->logError(__FILE__, __LINE__, "buffer not empty on eof");
					handleException();
				}
				break;
			}
			pos += bytes;
		}

		ssize_t newline;
		// Find the last '\n'
		for (newline = pos - 1; newline >= 0; --newline) {
			if (buf[newline] == '\n') {
				const char was = buf[newline + 1];
				buf[newline + 1] = '\0';
				buffer->kallsyms(currTime, buf);
				// Sleep 3 ms to avoid sending out too much data too quickly
				usleep(3000);
				buf[0] = was;
				// Assert the memory regions do not overlap
				if (pos - newline >= newline + 1) {
					logg->logError(__FILE__, __LINE__, "memcpy src and dst overlap");
					handleException();
				}
				if (pos - newline - 2 > 0) {
					memcpy(buf + 1, buf + newline + 2, pos - newline - 2);
				}
				pos -= newline + 1;
				break;
			}
		}
	}

	close(fd);

	return true;
}