1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <unistd.h>
4 #include <string.h>
5 #include <sys/ioctl.h>
6 #include <linux/perf_event.h>
7 #include <asm/unistd.h>
8 #include <sys/mman.h>
9 #include <sys/stat.h>
10 #include <poll.h>
11 #include <signal.h>
12 #include <fcntl.h>
13 #include <elf.h>
14 #include <string.h>
15 #include <asm/perf_regs.h>
16
17 #include <vector>
18 #include <string>
19 #include <map>
20 #include <unordered_map>
21 #include <algorithm>
22 using namespace std;
23
24
25 #define MAXN 1024
26 #define MAXCPU 1024
27 #define error(msg) do { perror(msg); exit(1); } while(0)
28 //--------------------------------symbols-------------------------------------------
29 typedef struct { unsigned long long addr, size, baddr, boffset; } FNode;
30 using STORE_T = unordered_map<string, FNode>;
31 /*
32 * load FUNC symbols refering to the section indicated by the offset, relocate the virtual address
33 */
parse_elf64(FILE * fp,STORE_T & store)34 void parse_elf64(FILE *fp, STORE_T& store) {
35 // printf("read elf with offset 0x%llx, addr 0x%llx\n", v_offset, v_addr);
36 Elf64_Ehdr ehdr;
37 FNode func;
38 int rc = fread(&ehdr, sizeof(ehdr), 1, fp);
39 if (rc != 1) return;
40 int n, s, i;
41 unsigned long long offset;
42
43 // load program headers
44 n = ehdr.e_phnum;
45 s = ehdr.e_phentsize;
46 offset = ehdr.e_phoff;
47 Elf64_Phdr phdr;
48 for (i=0; i<n; i++) {
49 rc = fseek(fp, offset, SEEK_SET);
50 if (rc<0) { perror("fail to seek"); return; }
51 rc = fread(&phdr, sizeof(phdr), 1, fp);
52 if (rc != 1) { perror("fail to read program header"); return; }
53 if (phdr.p_flags&PF_X) {
54 func.baddr=phdr.p_vaddr;
55 func.boffset=phdr.p_offset;
56 }
57 offset+=s;
58 }
59 // load section headers
60 n = ehdr.e_shnum;
61 s = ehdr.e_shentsize;
62 offset = ehdr.e_shoff;
63 Elf64_Shdr shdr;
64 vector<Elf64_Shdr> headers;
65 for (int i=0; i<n; i++) {
66 rc = fseek(fp, offset, SEEK_SET);
67 if (rc<0) { perror("fail to seek"); return; }
68 rc = fread(&shdr, sizeof(shdr), 1, fp);
69 if (rc != 1) { perror("fail to read sec header"); return; }
70 headers.push_back(shdr);
71 offset+=s;
72 }
73 Elf64_Sym symb;
74 // TODO: remove symbols which need relocation
75 Elf64_Rel rel;
76 Elf64_Rela rela;
77 unsigned long long faddr, fsize;
78 unsigned long long size, item_size;
79 int link, ix, flink, k;
80 char fname[128];
81 for (int i=0; i<n; i++) {
82 offset = headers[i].sh_offset;
83 size = headers[i].sh_size;
84 item_size = headers[i].sh_entsize;
85 link = headers[i].sh_link;
86 switch(headers[i].sh_type) {
87 case SHT_DYNSYM:
88 case SHT_SYMTAB:
89 if (link<=0) break;
90 for (k=0; k+item_size<=size; k+=item_size) {
91 rc = fseek(fp, offset+k, SEEK_SET); if (rc<0) continue;
92 rc = fread(&symb, sizeof(symb), 1, fp); if (rc != 1) continue;
93 if (ELF64_ST_TYPE(symb.st_info) != STT_FUNC ) continue;
94 flink = symb.st_shndx; if (flink==0) continue;
95 fsize = symb.st_size;
96 faddr = symb.st_value;
97 ix = symb.st_name; if (ix==0) continue;
98 rc = fseek(fp, headers[link].sh_offset+ix, SEEK_SET); if (rc<0) continue;
99 if (fgets(fname, sizeof(fname), fp)==NULL) continue;
100 func.addr=faddr; func.size=fsize;
101 store[string(fname)] = func;
102 }
103 break;
104 default:
105 break;
106 }
107 }
108 }
109
load_symbol_from_file(const char * path)110 STORE_T* load_symbol_from_file(const char *path) {
111 // printf("loading symble from %s\n", path);
112 STORE_T *store = NULL;
113 FILE *fp = fopen(path, "rb");
114 if (fp==NULL) { perror("fail to open file"); return NULL; }
115 char ident[EI_NIDENT], c;
116 int err=0;
117 int rc = fread(ident, sizeof(ident), 1, fp);
118 if (rc != 1) { perror("fail to read ident"); err=-1; goto end; }
119 if (ident[0]!=0x7f) { printf("not a elf file\n"); err=-1; goto end; }
120 c=ident[4];
121 rc = fseek(fp, 0, SEEK_SET); if (rc<0) { perror("fail to rewind"); goto end; }
122 if (c == ELFCLASS32) {
123 printf("32bit elf not supported yet\n"); err=-2; goto end;
124 } else if (c == ELFCLASS64) {
125 store = new STORE_T();
126 parse_elf64(fp, *store);
127 }
128
129 end:
130 fclose(fp);
131 return store;
132 }
133
134
135 //------------------------------perf profiler-------------------------
perf_event_open(struct perf_event_attr * perf_event,pid_t pid,int cpu,int group_fd,unsigned long flags)136 static long perf_event_open(struct perf_event_attr *perf_event, pid_t pid, int cpu, int group_fd, unsigned long flags) {
137 return syscall(__NR_perf_event_open, perf_event, pid, cpu, group_fd, flags);
138 }
139
140 struct pollfd polls[MAXCPU];
141 // res for cleanup
142 static long long psize;
143 map<int, pair<void*, long long>> res;
144
int_exit(int _)145 void int_exit(int _) {
146 for (auto x: res) {
147 auto y = x.second;
148 void* addr = y.first;
149 munmap(addr, (1+MAXN)*psize);
150 close(x.first);
151 }
152 exit(0);
153 }
154 /*
155 perf call chain process
156 For now, if a address would not be located to some function, the address would be skipped.
157 */
process_event(char * base,unsigned long long size,unsigned long long offset)158 int process_event(char *base, unsigned long long size, unsigned long long offset) {
159 struct perf_event_header* p = NULL;
160 int pid, xpid;
161 unsigned long long rax, arg1, arg2, arg3, arg4;
162 offset%=size;
163 // assuming the header would fit within size
164 p = (struct perf_event_header*) (base+offset);
165 offset+=sizeof(*p); if (offset>=size) offset-=size;
166 if (p->type != PERF_RECORD_SAMPLE) return p->size;
167 // pid, tip;
168 pid = *((int *)(base+offset)); offset+=8; if (offset>=size) offset-=size;
169 unsigned long long abi = *(unsigned long long*)(base+offset); offset+=8; if (offset>=size) offset-=size;
170 if (abi == PERF_SAMPLE_REGS_ABI_64) {
171 rax = *(unsigned long long*)(base+offset); offset+=8; if (offset>=size) offset-=size; //rax
172 arg3 = *(unsigned long long*)(base+offset); offset+=8; if (offset>=size) offset-=size; //rdx
173 arg2 = *(unsigned long long*)(base+offset); offset+=8; if (offset>=size) offset-=size; //rsi
174 arg1 = *(unsigned long long*)(base+offset); offset+=8; if (offset>=size) offset-=size; //rdi
175 arg4 = *(unsigned long long*)(base+offset); offset+=8; if (offset>=size) offset-=size; //r10
176 printf("uprobe event: rax[0x%llx], f(%lld,...)\n", rax, arg1);
177 }
178 return p->size;
179 }
180
181 struct RNode {
182 string func_name, file_name;
183 unsigned long long vaddr, raddr;
184 };
main(int argc,char * argv[])185 int main(int argc, char *argv[]) {
186 char bb[256], *p, *func;
187 if (argc<3) { printf("usage: %s <pid> <func_name> \n", argv[0]); return 1; }
188 int pid = atoi(argv[1]);
189 func = argv[2];
190 if (pid<=0) { printf("invalid pid %d\n", pid); return 1; }
191 sprintf(bb, "/proc/%d/maps", pid);
192 FILE *fp = fopen(bb, "r");
193 if (fp==NULL) { printf("Fail to read proc for pid %d\n", pid); return 1; }
194 unsigned long long start, end, offset, inode;
195 char fname[128], mod[16], idx[32];
196 char ff[256];
197 char xx[64], xxx[32];
198 int i, valid, k;
199 int type = 0;
200 vector<RNode> cans;
201 RNode rr;
202 while(1) {
203 p = fgets(bb, sizeof(bb), fp); if (p==NULL) break;
204 if (sscanf(p, "%s %s %s %s %lld %s", xx, mod, xxx, idx, &inode, fname)!=6) continue;
205 for (i=0; i<4; i++) if (mod[i]=='x') break; if (i>=4) continue;
206 if (fname[0]!='/') continue;
207 valid=1;
208 start=0; for (i=0; xx[i]&&xx[i]!='-'; i++) {
209 if (xx[i]>='0'&&xx[i]<='9') start=start*16+xx[i]-'0';
210 else if (xx[i]>='A'&&xx[i]<='F') start=start*16+xx[i]-'A'+10;
211 else if (xx[i]>='a'&&xx[i]<='f') start=start*16+xx[i]-'a'+10;
212 else { valid=0; break; }
213 }
214 if (valid==0||start==0) continue;
215 end=0; for (i++; xx[i]; i++) {
216 if (xx[i]>='0'&&xx[i]<='9') end=end*16+xx[i]-'0';
217 else if (xx[i]>='A'&&xx[i]<='F') end=end*16+xx[i]-'A'+10;
218 else if (xx[i]>='a'&&xx[i]<='f') end=end*16+xx[i]-'a'+10;
219 else { valid=0; break; }
220 }
221 if (valid==0||start==0) continue;
222 offset=0; for (i=0; xxx[i]; i++) {
223 if (xxx[i]>='0'&&xxx[i]<='9') offset=offset*16+xxx[i]-'0';
224 else if (xxx[i]>='A'&&xxx[i]<='F') offset=offset*16+xxx[i]-'A'+10;
225 else if (xxx[i]>='a'&&xxx[i]<='f') offset=offset*16+xxx[i]-'a'+10;
226 else { valid=0; break; }
227 }
228 if (valid==0) break;
229 sprintf(ff, "/proc/%d/root%s", pid, fname);
230 STORE_T* s = load_symbol_from_file(ff);
231 if (s) {
232 for (auto x=s->begin(); x!=s->end(); x++) {
233 auto v = (*x).second;
234 if (strstr((*x).first.c_str(), func)) {
235 rr.func_name = (*x).first;
236 rr.file_name = string(ff);
237 rr.vaddr = v.addr-v.baddr+start;
238 rr.raddr = v.addr-v.baddr+v.boffset;
239 cans.push_back(rr);
240 }
241 }
242 delete s;
243 }
244 }
245 fclose(fp);
246
247 if (cans.size()==0) { printf("no func found\n"); return 0; }
248 k=0; if (cans.size()>1) {
249 printf("%d candidates found, please make a selection: [0, %d]\n", cans.size(), cans.size()-1);
250 for (i=0; i<cans.size(); i++) {
251 printf("[%d]: %s<%s>\n", i, cans[i].func_name.c_str(), cans[i].file_name.c_str());
252 }
253 scanf("%d", &k);
254 if (k<0||k>=cans.size()) { printf("invalid selection, abort\n"); return 1; }
255 }
256 printf("uprobing %s[%s] (vaddr 0x%llx, relative addr 0x%llx)\n", cans[k].func_name.c_str(), cans[k].file_name.c_str(), cans[k].vaddr, cans[k].raddr);
257 fp = fopen("/sys/bus/event_source/devices/uprobe/type", "r");
258 if (fp == NULL) { printf("fail to find type for kprobe\n"); return 1; }
259 fscanf(fp, "%d", &type);
260 fclose(fp);
261 if (type <= 0) { printf("unexpected type %d\n", type); return 1; }
262 // start perf event
263 psize = sysconf(_SC_PAGE_SIZE); // getpagesize();
264 int cpu_num = sysconf(_SC_NPROCESSORS_ONLN), fd;
265 struct perf_event_attr attr;
266 void *addr;
267 memset(&attr, 0, sizeof(attr));
268 attr.type = type;
269 attr.size = sizeof(attr);
270 attr.config = 0;
271 attr.sample_period = 1;
272 attr.wakeup_events = 1;
273 attr.sample_type = PERF_SAMPLE_TID|PERF_SAMPLE_REGS_USER; //PERF_SAMPLE_REGS_INTR;
274 attr.uprobe_path = (__u64)cans[k].file_name.c_str();
275 attr.probe_offset = cans[k].raddr;
276 attr.sample_regs_user = (1<<PERF_REG_X86_AX)|(1<<PERF_REG_X86_DI)|(1<<PERF_REG_X86_SI)|(1<<PERF_REG_X86_DX)|(1<<PERF_REG_X86_R10)|
277 (1<<PERF_REG_X86_R8)|(1<<PERF_REG_X86_R9);
278 for (i=0, k=0; i<cpu_num&&i<MAXCPU; i++) {
279 printf("attaching cpu %d\n", i);
280 fd = perf_event_open(&attr, pid, i, -1, PERF_FLAG_FD_CLOEXEC);
281 if (fd<0) { perror("fail to open perf event for pid"); continue; }
282 addr = mmap(NULL, (1+MAXN)*psize, PROT_READ, MAP_SHARED, fd, 0);
283 if (addr == MAP_FAILED) { perror("mmap failed"); close(fd); continue; }
284 res[fd] = make_pair(addr, 0);
285 polls[k].fd = fd;
286 polls[k].events = POLLIN;
287 polls[k].revents = 0;
288 k++;
289 }
290 if (k==0) { printf("no cpu event attached at all\n"); return 1; }
291
292 signal(SIGINT, int_exit);
293 signal(SIGTERM, int_exit);
294
295 unsigned long long head;
296 struct perf_event_mmap_page *mp;
297 while (poll(polls, k, -1)>0) {
298 // printf("wake\n");
299 for (i=0; i<k; i++) {
300 if ((polls[i].revents&POLLIN)==0) continue;
301 fd = polls[i].fd;
302 addr = res[fd].first;
303 mp = (struct perf_event_mmap_page *)addr;
304 head = res[fd].second;
305 if (head==mp->data_head) continue;
306 ioctl(fd, PERF_EVENT_IOC_PAUSE_OUTPUT, 1);
307 head = mp->data_head-((mp->data_head-head)%mp->data_size);
308 while(head<mp->data_head) head+=process_event((char*)addr+mp->data_offset, mp->data_size, head);
309 ioctl(fd, PERF_EVENT_IOC_PAUSE_OUTPUT, 0);
310 res[fd].second = mp->data_head;
311 }
312 }
313 int_exit(0);
314 return 0;
315 }
316