linux下实现在程序运行时的函数替换(热补丁)【转】

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
应用型负载均衡 ALB,每月750个小时 15LCU
简介: 转自:http://www.cnblogs.com/leo0000/p/5632642.html 声明:以下的代码成果,是参考了网上的injso技术,在本文的最后会给出地址,同时非常感谢injso技术原作者的分享。

转自:http://www.cnblogs.com/leo0000/p/5632642.html

声明:以下的代码成果,是参考了网上的injso技术,在本文的最后会给出地址,同时非常感谢injso技术原作者的分享。

   但是injso文章中的代码存在一些问题,所以后面出现的代码是经过作者修改和检测的。也正因为这些错误,加深了我的学习深度。

 

  最近因为在学习一些调试的技术,但是很少有提到如何在函数运行时实现函数替换的。

  为什么会想到这一点?因为在学习调试时,难免会看到一些内核方面的调试技术,内核中的调试有一个kprobe,很强大,可以实现运行时的函数替换。其原理就是hook,钩子,但是学习了这个kprobe之后会发现,kprobe内部有检测所要钩的函数是不是属于内核空间,必须是内核函数才能实现替换。而实际上,我的工作大部分还是在应用层的,所以想要实现应用程序的热补丁技术。

  一些基础的知识这边的就不展开了,需要的基础有,elf文件格式,ptrace,waitpid,应用程序间通信时的信号,汇编。

  • 1、elf文件加载过程

  elf简单地说是由以下四部分组成的,elf文件头,program header和section header,内容。其中program header是运行时使用的,而section header并不会被加载进程序运行空间,但他们可以在编译时被指定该段的加载地址等信息,当然一般这个链接脚本.lds是由gcc默认的。

  第一步,加载elf文件头,检验文件类型版本等,重要的是找到program header的地址和header的个数,如果连接器脚本是默认的,那么elf文件头会被加载在0x804800地址处。

  第二步,加载program header,接着扫描program header,找到一个类型为PT_INTERP的program header,这个header里面放着的是有关解释器的地址,这时候将解释器程序的elf文件头加载进来。一般是这样:

      INTERP 0x000134 0x08048134 0x08048134 0x00013 0x00013 R 0x1
        [Requesting program interpreter: /lib/ld-linux.so.2]

  第三步,扫描program header,如果类型为PT_LOAD,则将该段加载进来。

  第四步,判断是否需要解释器程序,如果需要,把解释器程序加载进来,并把程序入口设置为解释器程序的地址。否则是应用程序本身的入口。反汇编为_start标号。

  第五步,设置命令行传入的参数等应用程序需要的信息。

  第六步,解释器程序开始运行,加载程序需要的库,填写重定向符号表中的地址信息。

  • 2.elf文件动态链接过程

  上一步,解释器程序根据program header已经将应用程序的段都加载进内存了,接下来再扫描program header,找到类型为PT_DYNAMIC,这里面包含了很多由section header描述的内容,包括重定向表,符号表,字符串表等等。解释器需要这个段描述的一些信息。

  DT_NEEDED描述了所需要的动态库名称,DT_REL描述了重定位表地址,DT_JMPREL描述了重定位表地址(这个表是懒惰链接使用的),DT_PLTGOT全局偏移表地址。

  此时解释器程序就可以根据所需要的动态库,将其加载进内存。每一个被加载进来的库的相关信息会被记录在link_map结构中,这个结构是一个链表,保存了所有的动态信息。

  其中,全局偏移表got,got[0]保存了PT_DYNAMIC的起始地址,got[1]保存link_map的地址,而link_map中就可以找到PT_DYNAMIC的起始地址,和下一个或者上一个共享文件或者可执行文件的link_map地址。

  DT_REL这个重定向表中的符号必须在此时就被解析完成。

  而DT_JMPREL这个重定向表中的符号可以在运行时再解析。

  所有的库和符号全部解析完成之后,解释器程序就会把控制权交给可执行文件的_start。程序开始执行。

  • 3.替换函数和被替换函数

  被替换程序源码。 

1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include <time.h>
int  main()
{
         while (1){
                 sleep(10);
                 printf( "%d : original\n" ,time(0));
         }
}

  替换新库代码。

1
2
3
4
5
6
7
#include <stdio.h>
 
int  newmyprint()
{
     write(1, "hahahahahahaha" ,14);
     return  0;
}

  够简单明了吧,如果替换成功,目标程序将会一直输出“哈哈哈哈哈哈”。

  • 4.功能函数  

  ptrace相关代码:

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
/* 读进程寄存器 */
void  ptrace_readreg( int  pid,  struct  user_regs_struct *regs)
{
     if (ptrace(PTRACE_GETREGS, pid, NULL, regs))
         printf ( "*** ptrace_readreg error ***\n" );
     /*printf("ptrace_readreg\n");
     printf("%x\n",regs->ebx);
     printf("%x\n",regs->ecx);
     printf("%x\n",regs->edx);
     printf("%x\n",regs->esi);
     printf("%x\n",regs->edi);
     printf("%x\n",regs->ebp);
     printf("%x\n",regs->eax);
     printf("%x\n",regs->xds);
     printf("%x\n",regs->xes);
     printf("%x\n",regs->xfs);
     printf("%x\n",regs->xgs);
     printf("%x\n",regs->orig_eax);
     printf("%x\n",regs->eip);
     printf("%x\n",regs->xcs);
     printf("%x\n",regs->eflags);
     printf("%x\n",regs->esp);
     printf("%x\n",regs->xss);*/
 
}
 
/* 写进程寄存器 */
void  ptrace_writereg( int  pid,  struct  user_regs_struct *regs)
{
     /*printf("ptrace_writereg\n");
     printf("%x\n",regs->ebx);
     printf("%x\n",regs->ecx);
     printf("%x\n",regs->edx);
     printf("%x\n",regs->esi);
     printf("%x\n",regs->edi);
     printf("%x\n",regs->ebp);
     printf("%x\n",regs->eax);
     printf("%x\n",regs->xds);
     printf("%x\n",regs->xes);
     printf("%x\n",regs->xfs);
     printf("%x\n",regs->xgs);
     printf("%x\n",regs->orig_eax);
     printf("%x\n",regs->eip);
     printf("%x\n",regs->xcs);
     printf("%x\n",regs->eflags);
     printf("%x\n",regs->esp);
     printf("%x\n",regs->xss);*/
 
     if (ptrace(PTRACE_SETREGS, pid, NULL, regs))
         printf ( "*** ptrace_writereg error ***\n" );
}
 
/* 关联到进程 */
void  ptrace_attach( int  pid)
{
     if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) {
         perror ( "ptrace_attach" );
         exit (-1);
     }
 
     waitpid(pid, NULL,  /*WUNTRACED*/ 0);  
    
     ptrace_readreg(pid, &oldregs);
}
 
/* 进程继续 */
void  ptrace_cont( int  pid)
{
     int  stat;
 
     if (ptrace(PTRACE_CONT, pid, NULL, NULL) < 0) {
         perror ( "ptrace_cont" );
         exit (-1);
     }
     /*while(!WIFSTOPPED(stat))
         waitpid(pid, &stat, WNOHANG);*/
}
 
/* 脱离进程 */
void  ptrace_detach( int  pid)
{
     ptrace_writereg(pid, &oldregs);
 
     if (ptrace(PTRACE_DETACH, pid, NULL, NULL) < 0) {
         perror ( "ptrace_detach" );
         exit (-1);
     }
}
 
/* 写指定进程地址 */
void  ptrace_write( int  pid, unsigned  long  addr,  void  *vptr,  int  len)
{
     int  count;
     long  word;
 
     count = 0;
 
     while (count < len) {
         memcpy (&word, vptr + count,  sizeof (word));
         word = ptrace(PTRACE_POKETEXT, pid, addr + count, word);
         count += 4;
 
         if ( errno  != 0)
             printf ( "ptrace_write failed\t %ld\n" , addr + count);
     }
}
 
/* 读指定进程 */
int  ptrace_read( int  pid, unsigned  long  addr,  void  *vptr,  int  len)
{
     int  i,count;
     long  word;
     unsigned  long  *ptr = (unsigned  long  *)vptr;
 
     i = count = 0;
     //printf("ptrace_read addr = %x\n",addr);
     while  (count < len) {
         //printf("ptrace_read addr+count = %x\n",addr + count);
         word = ptrace(PTRACE_PEEKTEXT, pid, addr + count, NULL);
         while (word < 0)
         {
             if ( errno  == 0)
                 break ;
             //printf("ptrace_read word = %x\n",word);
             perror ( "ptrace_read failed" );
             return  2;
         }
         count += 4;
         ptr[i++] = word;
     }
     return  0;
}
 
/*
  在进程指定地址读一个字符串
  */
char  * ptrace_readstr( int  pid, unsigned  long  addr)
{
     char  *str = ( char  *)  malloc (64);
     int  i,count;
     long  word;
     char  *pa;
 
     i = count = 0;
     pa = ( char  *)&word;
 
     while (i <= 60) {
         word = ptrace(PTRACE_PEEKTEXT, pid, addr + count, NULL);
         count += 4;
 
         if  (pa[0] == 0) {
             str[i] = 0;
         break ;
         }
         else
             str[i++] = pa[0];
 
         if  (pa[1] == 0) {
             str[i] = 0;
             break ;
         }
         else
             str[i++] = pa[1];
 
         if  (pa[2] ==0) {
             str[i] = 0;
             break ;
         }
         else
             str[i++] = pa[2];
 
         if  (pa[3] ==0) {
             str[i] = 0;
             break ;
         }
         else
             str[i++] = pa[3];
     }
    
     return  str;
}
 
 
 
 
/*
  将指定数据压入进程堆栈并返回堆栈指针
  */
void  * ptrace_push( int  pid,  void  *paddr,  int  size)
{
     unsigned  long  esp;
     struct  user_regs_struct regs;
 
     ptrace_readreg(pid, &regs);
     esp = regs.esp;
     esp -= size;
     esp = esp - esp % 4;
     regs.esp = esp;
 
     ptrace_writereg(pid, &regs);
 
     ptrace_write(pid, esp, paddr, size);
 
     return  ( void  *)esp;
}
 
/*
  在进程内调用指定地址的函数
  */
void  ptrace_call( int  pid, unsigned  long  addr)
{
     void  *pc;
     struct  user_regs_struct regs;
     int  stat;
     void  *pra;
 
     pc = ( void  *) 0x41414140;
     pra = ptrace_push(pid, &pc,  sizeof (pc));
 
     ptrace_readreg(pid, &regs);
     regs.eip = addr;
     ptrace_writereg(pid, &regs);
 
     ptrace_cont(pid);
     //while(WIFSIGNALED(stat))
        // waitpid(pid, &stat, WNOHANG);
}

  这里面的东西我就不展开了,对ptrace的学习,请自行man。

  

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
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
/*
因为应用程序可能不存在hash表,所以通过读取源文件的section header获取符号表的入口数,
其实是被误导了,但也学习了hash表的作用,用来快速查找符号表中的信息和字符串表中的信息
*/
/*int getnchains(int pid,unsigned long base_addr)
{
     printf("getnchains enter \n");
     Elf32_Ehdr *ehdr = (Elf32_Ehdr *) malloc(sizeof(Elf32_Ehdr));      
     Elf32_Shdr *shdr = (Elf32_Shdr *)malloc(sizeof(Elf32_Shdr));
     unsigned long shdr_addr;
     int i = 0;
     int fd;
     char filename[1024] = {0};
     ptrace_read(pid, base_addr, ehdr, sizeof(Elf32_Ehdr));
     shdr_addr = base_addr + ehdr->e_shoff;
     //printf("getnchains ehdr->e_shoff\t %p\n", ehdr->e_shoff);
     
     snprintf(filename, sizeof(filename), "/proc/%d/exe", pid);
     fd = open(filename, O_RDONLY);
     if (lseek(fd, ehdr->e_shoff, SEEK_SET) < 0)
         exit(-1);
     
     /*while(i<ehdr->e_shnum)
     {
         read(fd, shdr, ehdr->e_shentsize);
         printf("getnchains i = %d\n",i);
         printf("getnchains shdr->sh_type = %x\n",shdr->sh_type);
         printf("getnchains shdr->sh_name = %x\n",shdr->sh_name);
         printf("getnchains shdr->sh_size = %x\n",shdr->sh_size);
         printf("getnchains shdr->sh_entsize = %x\n",shdr->sh_entsize);
         i++;
     }
     
     while(shdr->sh_type != SHT_SYMTAB)
         read(fd, shdr, ehdr->e_shentsize);
     nchains = shdr->sh_size/shdr->sh_entsize;
     //printf("getnchains shdr->sh_type = %d\n",shdr->sh_type);
     //printf("getnchains shdr->sh_name = %d\n",shdr->sh_name);
     //printf("getnchains shdr->sh_size = %d\n",shdr->sh_size);
     //printf("getnchains shdr->sh_entsize = %d\n",shdr->sh_entsize);
     //printf("getnchains nchains = %x\n",nchains); 
     close(fd);
     free(ehdr);
     free(shdr);
     printf("getnchains exit \n");
}
*/
 
 
/*
  取得指向link_map链表首项的指针
  */
struct  link_map * get_linkmap( int  pid)
{
     Elf32_Ehdr *ehdr = (Elf32_Ehdr *)  malloc ( sizeof (Elf32_Ehdr));      
     Elf32_Phdr *phdr = (Elf32_Phdr *)  malloc ( sizeof (Elf32_Phdr));
     Elf32_Dyn  *dyn =  (Elf32_Dyn *)  malloc ( sizeof (Elf32_Dyn));
     Elf32_Word got;
     struct  link_map *map = ( struct  link_map *) malloc ( sizeof ( struct  link_map));
     int  i = 1;
     unsigned  long  tmpaddr;
 
     ptrace_read(pid, IMAGE_ADDR, ehdr,  sizeof (Elf32_Ehdr));
     phdr_addr = IMAGE_ADDR + ehdr->e_phoff;
     printf ( "phdr_addr\t %p\n" , phdr_addr);
 
     ptrace_read(pid, phdr_addr, phdr,  sizeof (Elf32_Phdr));
     while (phdr->p_type != PT_DYNAMIC)
         ptrace_read(pid, phdr_addr +=  sizeof (Elf32_Phdr), phdr, sizeof (Elf32_Phdr));
     dyn_addr = phdr->p_vaddr;
     printf ( "dyn_addr\t %p\n" , dyn_addr);
 
     ptrace_read(pid, dyn_addr, dyn,  sizeof (Elf32_Dyn));
     while (dyn->d_tag != DT_PLTGOT) {
         tmpaddr = dyn_addr + i *  sizeof (Elf32_Dyn);
         //printf("get_linkmap tmpaddr = %x\n",tmpaddr);
         ptrace_read(pid,tmpaddr, dyn,  sizeof (Elf32_Dyn));
         i++;
     }
 
     got = (Elf32_Word)dyn->d_un.d_ptr;
     got += 4;
     //printf("GOT\t\t %p\n", got);
 
     ptrace_read(pid, got, &map_addr, 4);
     printf ( "map_addr\t %p\n" , map_addr);
     map = map_addr;
     //ptrace_read(pid, map_addr, map, sizeof(struct link_map));
    
     free (ehdr);
     free (phdr);
     free (dyn);
 
     return  map;
}
 
/*
  取得给定link_map指向的SYMTAB、STRTAB、HASH、JMPREL、PLTRELSZ、RELAENT、RELENT信息
  这些地址信息将被保存到全局变量中,以方便使用
  */
void  get_sym_info( int  pid,  struct  link_map *lm)
{
     Elf32_Dyn *dyn = (Elf32_Dyn *)  malloc ( sizeof (Elf32_Dyn));
     unsigned  long  dyn_addr;
     //printf("get_sym_info lm = %x\n",lm);
     //printf("get_sym_info lm->l_ld's offset = %x\n",&((struct link_map *)0)->l_ld);
     //printf("get_sym_info &lm->l_ld = %x\n",&(lm->l_ld));
     //dyn_addr = (unsigned long)&(lm->l_ld);
     //进入被跟踪进程获取动态节的地址  
     ptrace_read(pid,&(lm->l_ld) , &dyn_addr,  sizeof (dyn_addr));
     ptrace_read(pid,&(lm->l_addr) , &link_addr,  sizeof (dyn_addr));
     ptrace_read(pid, dyn_addr, dyn,  sizeof (Elf32_Dyn));
     //if(link_addr == 0)
     //  getnchains(pid,IMAGE_ADDR);
     /*else
         getnchains(pid,link_addr);*/
     while (dyn->d_tag != DT_NULL){
         //printf("get_sym_info dyn->d_tag = %x\n",dyn->d_tag);
         //printf("get_sym_info dyn->d_un.d_ptr = %x\n",dyn->d_un.d_ptr);
         switch (dyn->d_tag)
         {
         case  DT_SYMTAB:
             symtab = dyn->d_un.d_ptr;
             
             break ;
         case  DT_STRTAB:
             strtab = dyn->d_un.d_ptr;
             break ;
         /*case DT_HASH://可能不存在哈希表,此时nchains是错误的,这个值可以通过符号表得到
             //printf("get_sym_info hash table's addr = %x\n",dyn->d_un.d_ptr);
             //printf("get_sym_info symtbl's entry = %x\n",(dyn->d_un.d_ptr) + 4);
             ptrace_read(pid, (dyn->d_un.d_ptr) + 4,&nchains, sizeof(nchains));
             break;*/
         case  DT_JMPREL:
             jmprel = dyn->d_un.d_ptr;
             break ;
         case  DT_PLTRELSZ:
             totalrelsize = dyn->d_un.d_val;
             break ;
         case  DT_RELAENT:
             relsize = dyn->d_un.d_val;
             break ;
         case  DT_RELENT:
             relsize = dyn->d_un.d_val;
             break ;
         case  DT_REL:
             reldyn = dyn->d_un.d_ptr;       
             break ;
         case  DT_RELSZ:
             reldynsz = dyn->d_un.d_val;
             break ;
         }
         ptrace_read(pid, dyn_addr +=  sizeof (Elf32_Dyn), dyn,  sizeof (Elf32_Dyn));
     }
     
     //printf("get_sym_info link_addr = %x\n",link_addr);
     //printf("get_sym_info symtab = %x\n",symtab);
     //printf("get_sym_info relsize = %x\n",relsize);
     //printf("get_sym_info reldyn = %x\n",reldyn);
     //printf("get_sym_info totalrelsize = %x\n",totalrelsize);
     //printf("get_sym_info jmprel = %x\n",jmprel);
     //printf("get_sym_info nchains = %x\n",nchains);
     //printf("get_sym_info strtab = %x\n",strtab);
 
     nrels = totalrelsize / relsize;
     nreldyns = reldynsz/relsize;
     
     //printf("get_sym_info nreldyns = %d\n",nreldyns);
     //printf("get_sym_info nrels = %d\n",nrels);
 
     free (dyn);
     printf ( "get_sym_info exit\n" );
}
/*
  在指定的link_map指向的符号表查找符号,它仅仅是被上面的find_symbol使用
  */
unsigned  long   find_symbol_in_linkmap( int  pid,  struct  link_map *lm,  char  *sym_name)
{
     Elf32_Sym *sym = (Elf32_Sym *)  malloc ( sizeof (Elf32_Sym));
     int  i = 0;
     char  *str;
     unsigned  long  ret;
     int  flags = 0;
 
     get_sym_info(pid, lm);
    
     do {
         if (ptrace_read(pid, symtab + i *  sizeof (Elf32_Sym), sym,  sizeof (Elf32_Sym)))
             return  0;
         i++;
         //printf("find_symbol_in_linkmap sym->st_name = %x\tsym->st_size = %x\tsym->st_value = %x\n",sym->st_name,sym->st_size,sym->st_value);
         //printf("find_symbol_in_linkmap Elf32_Sym's size = %d\n",sizeof(Elf32_Sym));
         //printf("\nfind_symbol_in_linkmap sym->st_name = %x\n",sym->st_name);       
         if  (!sym->st_name && !sym->st_size && !sym->st_value) //全为0是符号表的第一项
             continue ;
         //printf("\nfind_symbol_in_linkmap strtab = %x\n",strtab);
         str = ( char  *) ptrace_readstr(pid, strtab + sym->st_name);
         //printf("\nfind_symbol_in_linkmap str = %s\n",str);
         //printf("\nfind_symbol_in_linkmap sym->st_value = %x\n",sym->st_value);
         if  ( strcmp (str, sym_name) == 0) {
             printf ( "\nfind_symbol_in_linkmap str = %s\n" ,str);
             printf ( "\nfind_symbol_in_linkmap sym->st_value = %x\n" ,sym->st_value);
             free (str);
             if (sym->st_value == 0) //值为0代表这个符号本身就是重定向的内容
                 continue ;
             flags = 1;
             
             //str = ptrace_readstr(pid, (unsigned long)lm->l_name);
             //printf("find_symbol_in_linkmap lib name [%s]\n", str);
             //free(str);
             break ;
         }
         
         free (str);
     } while (1);
 
 
     if  (flags != 1)
         ret = 0;
     else
         ret =  link_addr + sym->st_value;
 
     free (sym);
 
     return  ret;
}
 
/*
  解析指定符号
  */
unsigned  long   find_symbol( int  pid,  struct  link_map *map,  char  *sym_name)
{
     struct  link_map *lm = map;
     unsigned  long  sym_addr;
     char  *str;
     unsigned  long  tmp;
    
     //sym_addr = find_symbol_in_linkmap(pid, map, sym_name);
     //return 0;
     //if (sym_addr)
      //   return sym_addr;
     //printf("\nfind_symbol map = %x\n",map);
     //ptrace_read(pid,(char *)map+12,&tmp,4);
     //lm = tmp;
     //printf("find_symbol lm = %x\n",lm);
     //ptrace_read(pid, (unsigned long)map->l_next, lm, sizeof(struct link_map));
     sym_addr = find_symbol_in_linkmap(pid, lm, sym_name);
     while (!sym_addr ) {
         ptrace_read(pid, ( char  *)lm+12, &tmp, 4); //获取下一个库的link_map地址
         if (tmp == 0)
             return  0;
         lm = tmp;
         //printf("find_symbol lm = %x\n",lm);
         /*str = ptrace_readstr(pid, (unsigned long)lm->l_name);
         if(str[0] == '/0')
             continue;
         printf("[%s]\n", str);
         free(str);*/
 
         if  ((sym_addr = find_symbol_in_linkmap(pid, lm, sym_name)))
             break ;
     }
 
     return  sym_addr;
}
 
 
/* 查找符号的重定位地址 */
unsigned  long   find_sym_in_rel( int  pid,  char  *sym_name)
{
     Elf32_Rel *rel = (Elf32_Rel *)  malloc ( sizeof (Elf32_Rel));
     Elf32_Sym *sym = (Elf32_Sym *)  malloc ( sizeof (Elf32_Sym));
     int  i;
     char  *str;
     unsigned  long  ret;
     struct  link_map *lm;
     lm = map_addr;
     
     //get_dyn_info(pid);
     do {
         get_sym_info(pid,lm);
         ptrace_read(pid, ( char  *)lm+12, &lm, 4);
         //首先查找过程连接的重定位表
         for (i = 0; i< nrels ;i++) {
             ptrace_read(pid, (unsigned  long )(jmprel + i *  sizeof (Elf32_Rel)),
                                                                      rel,  sizeof (Elf32_Rel));
             if (ELF32_R_SYM(rel->r_info)) {
                 ptrace_read(pid, symtab + ELF32_R_SYM(rel->r_info) *
                                                    sizeof (Elf32_Sym), sym,  sizeof (Elf32_Sym));
                 str = ptrace_readstr(pid, strtab + sym->st_name);
                 if  ( strcmp (str, sym_name) == 0) {
                     if (sym->st_value != 0){
                         free (str);
                         continue ;
                     }
                     modifyflag = 1;
                     free (str);
                     break ;
                 }
                 free (str);
             }
         }
         
         if (modifyflag == 1)
             break ;
         //没找到的话,再找在链接时就重定位的重定位表
         for (i = 0; i< nreldyns;i++) {
             ptrace_read(pid, (unsigned  long )(reldyn+ i *  sizeof (Elf32_Rel)),
                                                                      rel,  sizeof (Elf32_Rel));
             if (ELF32_R_SYM(rel->r_info)) {
                 ptrace_read(pid, symtab + ELF32_R_SYM(rel->r_info) *
                                                    sizeof (Elf32_Sym), sym,  sizeof (Elf32_Sym));
                 str = ptrace_readstr(pid, strtab + sym->st_name);
                 if  ( strcmp (str, sym_name) == 0) {
                     if (sym->st_value != 0){
                         free (str);
                         continue ;
                     }
                     modifyflag = 2;
                     free (str);
                     break ;
                 }
                 free (str);
             }
         }
         
         if (modifyflag == 2)
             break ;
         
     } while (lm);
     //printf("find_sym_in_rel flags = %d\n",flags);
     if  (modifyflag == 0)
         ret = 0;
     else
         ret =  link_addr + rel->r_offset;
     //printf("find_sym_in_rel link_addr = %x\t sym->st_value = %x\n",link_addr , sym->st_value);
     free (rel);
     free (sym);
 
     return  ret;
}
 
/*
  在进程自身的映象中(即不包括动态共享库,无须遍历link_map链表)获得各种动态信息
  */
/*void get_dyn_info(int pid)
{
     Elf32_Dyn *dyn = (Elf32_Dyn *) malloc(sizeof(Elf32_Dyn));
     int i = 0;
 
     ptrace_read(pid, dyn_addr + i * sizeof(Elf32_Dyn), dyn, sizeof(Elf32_Dyn));
     i++;
     while(dyn->d_tag){
         switch(dyn->d_tag)
         {
         case DT_SYMTAB:
             //puts("DT_SYMTAB");
             symtab = dyn->d_un.d_ptr;
             break;
         case DT_STRTAB:
             strtab = dyn->d_un.d_ptr;
             //puts("DT_STRTAB");
             break;
         case DT_JMPREL:
             jmprel = dyn->d_un.d_ptr;
             //puts("DT_JMPREL");
             //printf("jmprel\t %p\n", jmprel);
             break;
         case DT_PLTRELSZ:
             totalrelsize = dyn->d_un.d_val;
             //puts("DT_PLTRELSZ");
             break;
         case DT_RELAENT:
             relsize = dyn->d_un.d_val;
             //puts("DT_RELAENT");
             break;
         case DT_RELENT:
             relsize = dyn->d_un.d_val;
             //puts("DT_RELENT");
             break;
         }
 
         ptrace_read(pid, dyn_addr + i * sizeof(Elf32_Dyn), dyn, sizeof(Elf32_Dyn));
         i++;
     }
 
     nrels = totalrelsize / relsize;
 
     free(dyn);
}*/
 
/*void call_dl_open(int pid, unsigned long addr, char *libname)
{
     void *pRLibName;
     struct user_regs_struct regs;
 
     /*
       先找个空间存放要装载的共享库名,我们可以简单的把它放入堆栈
      
     pRLibName = ptrace_push(pid, libname, strlen(libname) + 1);
 
     /* 设置参数到寄存器
     ptrace_readreg(pid, &regs);
     regs.eax = (unsigned long) pRLibName;
     regs.ecx = 0x0;
     regs.edx = RTLD_LAZY;
     ptrace_writereg(pid, &regs);
 
     /* 调用_dl_open
     ptrace_call(pid, addr);
     puts("call _dl_open ok");
}*/
 
 
 
 
/*#define RTLD_LAZY 0x00001
#define RTLD_NOW    0x00002
#define RTLD_BINDING_MASK   0x3
#define RTLD_NOLOAD 0x00004
#define RTLD_DEEPBIND   0x00008
 
#define RTLD_GLOBAL 0x00100
 
#define RTLD_LOCAL  0
 
#define RTLD_NODELETE   0x01000 */
 
void  call__libc_dlopen_mode( int  pid, unsigned  long  addr,  char  *libname)
{
     void  *plibnameaddr;
 
     //printf("call__libc_dlopen_mode libname = %s\n",libname);
     //printf("call__libc_dlopen_mode addr = %x\n",addr);
     //将需要加载的共享库地址压栈
     plibnameaddr = ptrace_push(pid, libname,  strlen (libname) + 1);
     ptrace_push(pid,&mode, sizeof ( int ));
     ptrace_push(pid,&plibnameaddr, sizeof (plibnameaddr));
 
     /* 调用__libc_dlopen_mode */
     ptrace_call(pid, addr);
}
void  call_printf( int  pid, unsigned  long  addr,  char  *string)
{
     void  *paddr;
 
     paddr = ptrace_push(pid, string,  strlen (string) + 1);
     ptrace_push(pid,&paddr, sizeof (paddr));
 
     ptrace_call(pid, addr);
}

  作者所做的修改,读者可以对比文章最后的连接中的代码。

  这边对于程序的具体解释,就不具体展开了。

  需要注意的是,原来是采用_dl_open的方式加载库函数,但是ld库并没有这个符号导出。而libc库中导出了一个可以加载库的__libc_dlopen_mode函数。

  • 5.主函数

  先说一下流程,

  a.获取被跟踪进程的link_map地址

  b.根据link_map给出的信息,搜索符号表,遍历每一个link_map中的符号表,直到找到想要找的符号。这里是printf或者__libc_dlopen_mode函数

  c.将库路径包括库名称传递给调用__libc_dlopen_mode的函数,该函数即call__libc_dlopen_mode会把__libc_dlopen_mode函数需要的参数,路径和加载方式压栈,在让被跟踪进

   程开始运行之前,压入一个非法地址,当__libc_dlopen_mode返回时返回到一个非法地址时,就会发生中断,此时跟踪进程可以waitpid跟踪到。好,设置寄存器,并让被跟踪进程开

   始运行。打开库之后,被跟踪进程因中断而被跟踪进程再次获得控制权。

  d.再一次根据之前保存的link_map信息,当然完全可以直接用上一次搜索结果结束之后的link_map往后找,因为新库一定在最后,但是本文还是从头开始找,找到新库中的

   newmyprint地址。

  e.还是根据link_map信息查找printf的重定向地址,在rel.dyn节中,有关这个rel.dyn和rel.plt等节之间的关系,可以看我的其他博文。

  f.将newmyprint的地址填入printf的重定向地址。

  g.将被跟踪进程原先的寄存器设置回去,释放控制。

  h.被跟踪进程开始输出“哈哈哈哈哈”。

  上源码:

  

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
int  main( int  argc,  char  *argv[])
{
     int  pid;
     struct  link_map *map;
     char  sym_name[256];
     unsigned  long  sym_addr;
     unsigned  long  new_addr,old_addr,rel_addr;
     int  status = 0;
     char  libpath[1024];
     char  oldfunname[128];
     char  newfunname[128];
     //mode = atoi(argv[2]);
     if (argc < 5){
         printf ( "usage : ./injso pid libpath oldfunname newfunname\n" );
         exit (-1);
     }
     /* 从命令行取得目标进程PID*/
     pid =  atoi (argv[1]);
     
     /* 从命令行取得新库名称*/
     memset (libpath,0, sizeof (libpath));
     memcpy (libpath,argv[2], strlen (argv[2]));
     
     /* 从命令行取得旧函数的名称*/
     memset (oldfunname,0, sizeof (oldfunname));
     memcpy (oldfunname,argv[3], strlen (argv[3]));
     
     /* 从命令行取得新函数的名称*/
     memset (newfunname,0, sizeof (newfunname));
     memcpy (newfunname,argv[4], strlen (argv[4]));
 
     printf ( "main pid = %d\n" ,pid);
     printf ( "main libpath : %s\n" ,libpath);
     printf ( "main oldfunname : %s\n" ,oldfunname);
     printf ( "main newfunname : %s\n" ,newfunname);
     /* 关联到目标进程*/
     ptrace_attach(pid);
    
     /* 得到指向link_map链表的指针 */
     map = get_linkmap(pid);                     /* get_linkmap */
 
     
     sym_addr = find_symbol(pid, map,  "printf" );      
     printf ( "found printf at addr %p\n" , sym_addr); 
     if (sym_addr == 0)
         goto  detach;
     call_printf(pid,sym_addr, "injso successed\n" );
     waitpid(pid,&status,0);
     printf ( "status = %x\n" ,status);
     
     /*ptrace_writereg(pid, &oldregs);
     ptrace_cont(pid);
 
     
 
     waitpid(pid,&status,0);
     //printf("status = %x\n",status);
     //ptrace_readreg(pid, &oldregs);
     //oldregs.eip = 0x8048414;
     //ptrace_writereg(pid, &oldregs);
     ptrace_cont(int pid)(pid);
     
     ptrace_detach(pid);
 
     exit(0);*/
     
     /* 发现__libc_dlopen_mode,并调用它 */
     sym_addr = find_symbol(pid, map,  "__libc_dlopen_mode" );         /* call _dl_open */
     printf ( "found __libc_dlopen_mode at addr %p\n" , sym_addr); 
     if (sym_addr == 0)
         goto  detach;
     call__libc_dlopen_mode(pid, sym_addr,libpath);     /* 注意装载的库地址 */  
     //while(1);
     waitpid(pid,&status,0);
     /* 找到新函数的地址 */
     strcpy (sym_name, newfunname);                 /* intercept */
     sym_addr = find_symbol(pid, map, sym_name);
     printf ( "%s addr\t %p\n" , sym_name, sym_addr);
     if (sym_addr == 0)
         goto  detach;
 
     /* 找到旧函数在重定向表的地址 */
     strcpy (sym_name, oldfunname);              
     rel_addr = find_sym_in_rel(pid, sym_name);
     printf ( "%s rel addr\t %p\n" , sym_name, rel_addr);
     if (rel_addr == 0)
         goto  detach;
 
     /* 找到用于保存read地址的指针 */
     //strcpy(sym_name, "oldread");              
     //old_addr = find_symbol(pid, map, sym_name);
     //printf("%s addr\t %p\n", sym_name, old_addr);
 
     /* 函数重定向 */
     puts ( "intercept..." );                     /* intercept */
     //ptrace_read(pid, rel_addr, &new_addr, sizeof(new_addr));
     //ptrace_write(pid, old_addr, &new_addr, sizeof(new_addr));
     //rel_addr = 0x8048497;如果是静态地址,也就是未导出该符号地址,那么只能通过反汇编先找到该函数被调用的地方,将这个地方的跳转地址修改
     
     if (modifyflag == 2)
         sym_addr = sym_addr - rel_addr - 4;
     printf ( "main modify sym_addr = %x\n" ,sym_addr);
     ptrace_write(pid, rel_addr, &sym_addr,  sizeof (sym_addr));
     
     puts ( "injectso ok" );
detach:
     printf ( "prepare to detach\n" );
     ptrace_detach(pid);
     
     return  0;
   
}

  这里面有一个很重要的地方,如果不先在目标进程中调用printf就不能够调用__lib_dlopen_mode成功,这个原因很奇怪,根据当时的core文件来看崩溃在了下面的这个函数,原因是_dl_open_hook这个全局变量为0,但实际上运行过printf之后,这个_dl_open_hook还是0。这个有待后续检验。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void  *
__libc_dlsym ( void  *map,  const  char  *name)
{
   struct  do_dlsym_args args;
   args.map = map;
   args.name = name;
 
#ifdef SHARED
   if  (__builtin_expect (_dl_open_hook != NULL, 0))
     return  _dl_open_hook->dlsym (map, name);
#endif
   return  (dlerror_run (do_dlsym, &args) ? NULL
       : ( void  *) (DL_SYMBOL_ADDRESS (args.loadbase, args.ref)));
}

  运行结果:

root@leo-desktop:injso# ./test
1467364356 : original
injso successed
hahahahahahahahahahahahahaha

  • 6.如何替换未导出符号的地址

  被替换函数源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
 
 
//int fun2();
 
int  fun1()
{
         printf ( "fun1\n" );
//      fun2();
}
 
int  main()
{
         signed  int  i  = 0x40011673 ;
         i = i - 0x4001172d ;
         printf ( "i = %x\n" ,i);
         while (1){
                 i = fun1();
                 sleep(10);
         }
         return  1;
}

  这个怎么来替换fun1函数的地址呢?

  首先反汇编得到main的机器码,如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
08048468 <main>:
  8048468:       55                      push   %ebp
  8048469:       89 e5                   mov    %esp,%ebp
  804846b:       83 e4 f0                and    $0xfffffff0,%esp
  804846e:       83 ec 20                sub    $0x20,%esp
  8048471:       c7 44 24 1c 73 16 01    movl   $0x40011673,0x1c(%esp)
  8048478:       40
  8048479:       81 6c 24 1c 2d 17 01    subl   $0x4001172d,0x1c(%esp)
  8048480:       40
  8048481:       b8 75 85 04 08          mov    $0x8048575,%eax
  8048486:       8b 54 24 1c             mov    0x1c(%esp),%edx
  804848a:       89 54 24 04             mov    %edx,0x4(%esp)
  804848e:       89 04 24                mov    %eax,(%esp)
  8048491:       e8 ce fe ff ff          call   8048364 < printf @plt>
  8048496:       e8 b9 ff ff ff          call   8048454 <fun1>
  804849b:       89 44 24 1c             mov    %eax,0x1c(%esp)
  804849f:       c7 04 24 0a 00 00 00    movl   $0xa,(%esp)
  80484a6:       e8 c9 fe ff ff          call   8048374 <sleep@plt>
  80484ab:       eb e9                   jmp    8048496 <main+0x2e>
  80484ad:       90                      nop
  80484ae:       90                      nop
  80484af:       90                      nop

  可以看到在地址0x8048496处的机器码是跳转到fun1函数的,那么这个ffffffb9就是call的操作数,操作数地址0x8048497,也就是说把这个地址中的数值改掉就可以了,有关这个call或者jmp的地址计算可以查看我的另外一篇博文。

  有关这个如何跳转的方法,已经在主函数的代码中给出了,但是被我注释掉了,大家感兴趣的话,可以自己试试。

  效果:

root@leo-desktop:lib2lib# ./a.out 
i = ffffff46
fun1
injso successed
hahahahahahaha^C

  这里面的无关代码,大家仔细看,是为了证明call的函数地址计算方式的。

  • 7.总结

  那么讲到现在的话,已经实现了不管函数符号是否导出都可以实现运行时替换的代码。

  这里面主要的技术是,elf文件格式,运行时加载的过程,跳转地址的计算,运行时链接的过程,也就是plt表(当然这个也可以从我的另一篇博文中看到)。

  比较遗憾的是有关那个奔溃,有网友如果找到了原因,请回复下,3q。当然我也会自己再研究下。

 

   最后补上全局变量和头文件:

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
#include <stdio.h>
#include <string.h>
#include <elf.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/errno.h>
#include <sys/user.h>
#include <link.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <bits/dlfcn.h>
 
#define IMAGE_ADDR 0x08048000
 
int  mode = 2;
 
struct  user_regs_struct oldregs;
Elf32_Addr phdr_addr;
Elf32_Addr dyn_addr;
Elf32_Addr map_addr;
Elf32_Addr symtab;
Elf32_Addr strtab;
Elf32_Addr jmprel;
Elf32_Addr reldyn;
Elf32_Word reldynsz;
Elf32_Word totalrelsize;
Elf32_Word relsize;
unsigned  long  link_addr;
int  nrels;
int  nreldyns;
//int nchains;
int  modifyflag = 0;
/*char libpath[128] = "/mnt/hgfs/svnroot/test/injectsov2/prj_linux/so.so";*/

  

 

  • 8.修正

  针对在调用__libc_dlopen_mode函数之前需要调用printf的问题,终于让我在晚上解决了。
  首先,我尝试了调用其他函数而不是printf函数,发现效果一样,包括第一次是调用__libc_dlopen_mode,第二次对该函数的调用都可以成功。
  其次,那么现在问题就集中在了这两个__libc_dlopen_mode调用之间的差别在哪里,程序段肯定是一致的,栈也是一致的,而堆空间未使用,还有一个重要的因素,那就是寄存器。
  最后,发现在调用__libc_dlopen_mode前,有四个寄存器不同,分别是eax,orig_eax,eflags和esp。我一开始认为,通用寄存器eax和orig_eax不会对程序的执行造成影响。但是通过实验,仅调一次__libc_dlopen_mode,部分寄存器赋正确执行时的值,发现对eax和orig_eax被赋于正确执行时的值时,程序可以正常运行,而且不仅仅必须是一种值,比如eax可以是0,1,0xffffffff,很多值都可以,但是被赋予0xfffffdfc和0xfffffdff等值时会失败,试验过并不是因为d这一位决定的,0xfffffdf0或者d00是可以运行成功的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(gdb) disassemble __libc_dlopen_mode
Dump of assembler code  for  function __libc_dlopen_mode:
    0x00232640 <+0>:   push   %ebp
    0x00232641 <+1>:   mov    %esp,%ebp
    0x00232643 <+3>:   sub    $0x1c,%esp
    0x00232646 <+6>:   mov    %ebx,-0x8(%ebp)
    0x00232649 <+9>:   mov    0x8(%ebp),%eax
    0x0023264c <+12>:  call   0x144a0f
    0x00232651 <+17>:  add    $0x519a3,%ebx
    0x00232657 <+23>:  mov    0xc(%ebp),%edx
    0x0023265a <+26>:  mov    %esi,-0x4(%ebp)
    0x0023265d <+29>:  mov    %eax,-0x14(%ebp)
    0x00232660 <+32>:  mov    %edx,-0x10(%ebp)
    0x00232663 <+35>:  mov    0x354c(%ebx),%esi
    0x00232669 <+41>:  test   %esi,%esi

  在实验中,还发现对eax赋于不正确的值时,当时忘了记了,还让程序跑飞了。崩了,但是新库已经加载上了。所以这个函数替换还是有一定的风险,或者说libc库本身存在一定的bug。
  所以现在问题找到了,在于eax和orig_eax上,但是对__libc_dlopen_mode反汇编发现,eax在函数开头就被赋予了通过栈传递的参数2的值,所以eax不应该影响程序的运行,但实际上影响了,这一点让我觉得很奇怪,如果有任何网友对这个原因知晓的话,麻烦回复,万分感谢。

 

  linux共享库注射地址:http://www.docin.com/p-634172083.html

 

  __simple原创

  转载请注明出处

【作者】 张昺华
【新浪微博】 张昺华--sky
【twitter】 @sky2030_
【facebook】 张昺华 zhangbinghua
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
高可用应用架构
欢迎来到“高可用应用架构”课程,本课程是“弹性计算Clouder系列认证“中的阶段四课程。本课程重点向您阐述了云服务器ECS的高可用部署方案,包含了弹性公网IP和负载均衡的概念及操作,通过本课程的学习您将了解在平时工作中,如何利用负载均衡和多台云服务器组建高可用应用架构,并通过弹性公网IP的方式对外提供稳定的互联网接入,使得您的网站更加稳定的同时可以接受更多人访问,掌握在阿里云上构建企业级大流量网站场景的方法。 学习完本课程后,您将能够: 理解高可用架构的含义并掌握基本实现方法 理解弹性公网IP的概念、功能以及应用场景 理解负载均衡的概念、功能以及应用场景 掌握网站高并发时如何处理的基本思路 完成多台Web服务器的负载均衡,从而实现高可用、高并发流量架构
目录
相关文章
|
2月前
|
安全 Linux Shell
Linux上执行内存中的脚本和程序
【9月更文挑战第3天】在 Linux 系统中,可以通过多种方式执行内存中的脚本和程序:一是使用 `eval` 命令直接执行内存中的脚本内容;二是利用管道将脚本内容传递给 `bash` 解释器执行;三是将编译好的程序复制到 `/dev/shm` 并执行。这些方法虽便捷,但也需谨慎操作以避免安全风险。
176 6
|
3月前
|
网络协议 Linux
Linux查看端口监听情况,以及Linux查看某个端口对应的进程号和程序
Linux查看端口监听情况,以及Linux查看某个端口对应的进程号和程序
629 2
|
3月前
|
Linux Python
linux上根据运行程序的进程号,查看程序所在的绝对路径。linux查看进程启动的时间
linux上根据运行程序的进程号,查看程序所在的绝对路径。linux查看进程启动的时间
66 2
|
1月前
|
运维 Java Linux
【运维基础知识】Linux服务器下手写启停Java程序脚本start.sh stop.sh及详细说明
### 启动Java程序脚本 `start.sh` 此脚本用于启动一个Java程序,设置JVM字符集为GBK,最大堆内存为3000M,并将程序的日志输出到`output.log`文件中,同时在后台运行。 ### 停止Java程序脚本 `stop.sh` 此脚本用于停止指定名称的服务(如`QuoteServer`),通过查找并终止该服务的Java进程,输出操作结果以确认是否成功。
34 1
|
2月前
|
消息中间件 分布式计算 Java
Linux环境下 java程序提交spark任务到Yarn报错
Linux环境下 java程序提交spark任务到Yarn报错
41 5
|
2月前
|
Linux Shell
Linux系统编程:掌握popen函数的使用
记得在使用完 `popen`打开的流后,总是使用 `pclose`来正确关闭它,并回收资源。这种做法符合良好的编程习惯,有助于保持程序的健壮性和稳定性。
89 6
|
2月前
|
Linux Shell
Linux系统编程:掌握popen函数的使用
记得在使用完 `popen`打开的流后,总是使用 `pclose`来正确关闭它,并回收资源。这种做法符合良好的编程习惯,有助于保持程序的健壮性和稳定性。
132 3
|
2月前
|
Linux
在Linux内核中根据函数指针输出函数名称
在Linux内核中根据函数指针输出函数名称
|
3月前
|
Linux PHP
Linux CentOS 宝塔 Suhosin禁用php5.6版本eval函数详细图文教程
【8月更文挑战第27天】本文介绍两种禁用PHP执行的方法:使用`PHP_diseval_extension`禁用和通过`suhosin`禁用。由于`suhosin`不支持PHP8,仅适用于PHP7及以下版本,若服务器安装了PHP5.6,则需对应安装`suhosin-0.9.38`版本。文章提供了详细的安装步骤,并强调了宝塔环境下与普通环境下的PHP路径差异。安装完成后,在`php.ini`中添加`suhosin.so`扩展并设置`executor.disable_eval = on`以禁用执行功能。最后通过测试代码验证是否成功禁用,并重启`php-fpm`服务生效。
43 2
|
3月前
|
NoSQL Linux C语言
嵌入式GDB调试Linux C程序或交叉编译(开发板)
【8月更文挑战第24天】本文档介绍了如何在嵌入式环境下使用GDB调试Linux C程序及进行交叉编译。调试步骤包括:编译程序时加入`-g`选项以生成调试信息;启动GDB并加载程序;设置断点;运行程序至断点;单步执行代码;查看变量值;继续执行或退出GDB。对于交叉编译,需安装对应架构的交叉编译工具链,配置编译环境,使用工具链编译程序,并将程序传输到开发板进行调试。过程中可能遇到工具链不匹配等问题,需针对性解决。