好的,因为我们中的一些人喜欢用鼻子吃意大利面,这是我过去写的一些实际代码:-)
首先,使用不支持它的语言获得可自我修改的代码将是非常重要的。
在一种不支持动态变量、函数名称的语言中允许使用动态变量、函数名称的想法非常简单。在程序的某个状态下,您需要一个动态的任何东西来自行修改您的代码并恢复执行
从你离开的地方。 eval(),也就是。
如果语言支持eval() 和这样的等价物,这一切都非常简单。但是,awk 没有这样的功能。因此,你,程序员必须提供这样的接口。
要让这一切发生,你有三个主要问题
- 如何获取我们的 self 以便我们修改它
- 如何加载修改后的代码,并从中断处继续
- 找到一种方法让解释器接受我们修改后的代码
如何获取我们的 self 以便我们修改它
这是一个示例代码,适合直接执行。
这是我为运行 gawk 的环境注入的 infastrucure,因为它需要 PROCINFO
echo ""| awk '
function push(d){stack[stack[0]+=1]=d;}
function pop(){if(stack[0])return stack[stack[0]--];return "";}
function dbg_printarray(ary , x , s,e, this , i ){
x=(x=="")?"A":x;for(i=((s)?s:1);i<=((e)?e:ary[0]);i++){print x"["i"]=["ary[i]"]"}}
function dbg_argv(A ,this,p){
A[0]=0;p="/proc/"PROCINFO["pid"]"/cmdline";push(RS);RS=sprintf("%c",0);
while((getline v <p)>0)A[A[0]+=1]=v;RS=pop();close(p);}
{
print "foo";
dbg_argv(A);
dbg_printarray(A);
print "bar";
}'
结果:
foo
A[1]=[awk]
A[2]=[
function push(d){stack[stack[0]+=1]=d;}
function pop(){if(stack[0])return stack[stack[0]--];return "";}
function dbg_printarray(ary , x , s,e, this , i ){
x=(x=="")?"A":x;for(i=((s)?s:1);i<=((e)?e:ary[0]);i++){print x"["i"]=["ary[i]"]"}}
function dbg_argv(A ,this,p){
A[0]=0;p="/proc/"PROCINFO["pid"]"/cmdline";push(RS);RS=sprintf("%c",0);
while((getline v <p)>0)A[A[0]+=1]=v;RS=pop();close(p);}
{
print "foo";
dbg_argv(A);
dbg_printarray(A);
print "bar";
}]
bar
如您所见,只要操作系统不玩我们的args,并且/proc/可用,就可以
阅读我们的自我。起初这可能看起来没用,但我们需要它来推送/弹出我们的堆栈,
这样我们的执行状态就可以嵌入到代码中,这样我们就可以保存/恢复并在操作系统关闭/重启时幸存下来
我省略了操作系统检测功能和引导加载程序(用 awk 编写),因为如果我发布它,
孩子们可以构建独立于平台的多正规代码,而且很容易造成破坏。
如何加载修改后的代码,并从中断处继续
现在,通常你有 push() 和 pop() 用于寄存器,所以你可以保存你的状态并玩
你的自我,并从你离开的地方继续。调用并读取您的堆栈是获取
内存地址。
不幸的是,在 awk 中,在正常情况下我们不能使用指针(没有很多脏活),
或寄存器(除非您可以在此过程中注入其他东西)。
但是,您需要一种方法来暂停和恢复您的代码。
这个想法很简单。而不是让 awk 控制你的循环和 while,if else 条件,
递归深度和你所在的功能,代码应该。
保留堆栈、变量名列表、函数名列表,并自行管理。
只要确保你的代码总是不断调用self_modify( bool ),这样即使突然失败,
一旦脚本重新运行,我们就可以输入self_modify( bool ) 并恢复我们的状态。
当您想自行修改代码时,您必须提供定制的
write_stack() 和 read_stack() 代码,将堆栈状态写为字符串,并从
从代码嵌入字符串本身中取出值,并恢复执行状态。
这是演示整个流程的一小段代码
echo ""| awk '
function push(d){stack[stack[0]+=1]=d;}
function pop(){if(stack[0])return stack[stack[0]--];return "";}
function dbg_printarray(ary , x , s,e, this , i ){
x=(x=="")?"A":x;for(i=((s)?s:1);i<=((e)?e:ary[0]);i++){print x"["i"]=["ary[i]"]"}}
function _(s){return s}
function dbg_argv(A ,this,p){
A[0]=0;p="/proc/"PROCINFO["pid"]"/cmdline";push(RS);RS=sprintf("%c",0);
while((getline v <p)>0)A[A[0]+=1]=v;RS=pop();close(p);}
{
_(BEGIN_MODIFY"|");print "#foo";_("|"END_MODIFY)
dbg_argv(A);
sub( \
"BEGIN_MODIFY\x22\x5c\x7c[^\x5c\x7c]*\x5c\x7c\x22""END_MODIFY", \
"BEGIN_MODIFY\x22\x7c\x22);print \"#"PROCINFO["pid"]"\";_(\x22\x7c\x22""END_MODIFY" \
,A[2])
print "echo \x22\x22\x7c awk \x27"A[2]"";
print "function bar_"PROCINFO["pid"]"_(s){print \x22""doe\x22}";
print "\x27"
}'
结果:
和我们原来的代码完全一样,除了
_(BEGIN_MODIFY"|");print "65964";_("|"ND_MODIFY)
和
function bar_56228_(s){print "doe"}
代码末尾
现在,这似乎没用,因为我们只是将代码 print "foo"; 替换为我们的 pid。
但它变得有用,当有多个 _() 带有单独的 MAGIC 字符串来识别 BLOCKS 时,
和一个定制的多行字符串替换例程,而不是sub()
您必须至少为堆栈、函数列表、执行点提供 BLOCKS。
注意最后一行包含bar
这本身只是一个刺痛,但是当这段代码重复执行时,请注意
function bar_56228_(s){print "doe"}
function bar_88128_(s){print "doe"}
...
而且它还在不断增长。虽然这个例子是故意制作的,所以它没有任何用处,
如果我们提供一个例程来调用bar_pid_(s) 而不是那个print "foo" 代码,
突然这意味着我们手上有eval() :-)
现在,是不是 eval() 有用:-)
不要忘记提供定制的 remove_block() 函数,以便代码维护
合理的大小,而不是每次执行时都增长。
找到一种方法让解释器接受我们修改后的代码
通常调用二进制文件是微不足道的。但是,当在 awk 中这样做时,它变得很困难。
你可能会说 system() 是一种方式。
这有两个问题。
- system() 可能不适用于某些环境
- 它会在您执行代码时阻塞,因为您无法执行递归调用并同时让用户满意。
如果您必须使用system(),请确保它不会阻塞。
对system("sleep 20 && echo from-sh & ") 的正常呼叫将不起作用。
解决方法很简单,
echo ""|awk '{print "foo";E="echo ep ; sleep 20 && echo foo & disown ; "; E | getline v;close(E);print "bar";}'
现在你有一个不阻塞的异步 system() 调用:-)