PTRACE的权限检查,自由是有前提的~

系统调用PTRACE的一切开始于这里:
COMPAT_SYSCALL_DEFINE4(ptrace, compat_long_t, request, compat_long_t, pid,
               compat_long_t, addr, compat_long_t, data)

首先提供了attach的绿色通道:
if (request == PTRACE_ATTACH || request == PTRACE_SEIZE) {
        ret = ptrace_attach(child, request, addr, data);
attach做的事情就是把当前进程变成了被trace进程的父进程,同时把被trace进程加入到当前进程的ptraced链表中;
__ptrace_link(task, current);
list_add(&child->ptrace_entry, &new_parent->ptraced);
    child->parent = new_parent;
但是这样做是有前提的,具体请看后面的分析,这里先按下不表。

attach?wtf?为啥要在用之前attach,搞得这么神秘,难道直接用不可以吗?
不可以,因为你不能直接访问其他进程的地址空间,否则地址空间的隔离不就变成笑话了。
所以这个ptrace系统调用入口做了个很重要的check:
ret = ptrace_check_attach(child, request == PTRACE_KILL ||
                  request == PTRACE_INTERRUPT);
    if (!ret) { //只有返回为0,即判断条件成立,才继续执行对ptrace的request
        ret = compat_arch_ptrace(child, request, addr, data);
        if (ret || request != PTRACE_DETACH)
            ptrace_unfreeze_traced(child);
    }
child->ptrace && child->parent == current
只有过了这一关,才能进行后面的操作(除了kil和interrupt,他们自己的处理逻辑里面有此类的判断或者无需),那难道随便就可以attach,那我就麻烦一点先attach呗;显然不会这么傻,前面卖了个关子,这里来看看attach里面做的权限检查,必须是有权的人才能随便搞的,屁民是要守法的!!

权限管理的水比较深,博主还没吃透,所以这里先简单列一下我能看懂的部分:
etval = __ptrace_may_access(task, PTRACE_MODE_ATTACH);
这里有两个条件,1)我自己人改自己人ok(这里的suid euid等等的请看后面链接);2)我的上层命名空间ok(我理解是,比如像root用户这种大boss)
    tcred = __task_cred(task);
    if (uid_eq(cred->uid, tcred->euid) &&
        uid_eq(cred->uid, tcred->suid) &&
        uid_eq(cred->uid, tcred->uid)  &&
        gid_eq(cred->gid, tcred->egid) &&
        gid_eq(cred->gid, tcred->sgid) &&
        gid_eq(cred->gid, tcred->gid))
        goto ok;
    if (ptrace_has_cap(tcred->user_ns, mode))
        goto ok;
另外还要做命名空间能力的检测,看看目标进程的命名空间有没有被ptrace的能力:
ns_capable(__task_cred(task)->user_ns, CAP_SYS_PTRACE)

参考链接

修改运行中代码之神器——PTRACE的秘诀

ptrace是个神奇的系统调用,是gdb等工具的实现基础,它可以用来监控进程的执行,读取或修改进程的寄存器和memory内容包括代码段。有的底层码农老司机甚至也会觉得奇怪,代码段不都是不可写的吗,为啥ptrace就能修改呢?

下面解释一下,老司机直接看后面代码秒懂:
  • 首先,代码段不可写,其实是在页表中对代码段相应的页设置了不可写的flag,而页表只能控制进程地址空间的访问权限。
  • 其次,这里的不可写只对用户态进程起作用。当然,进程的地址空间是隔离的,而进程在用户态只能通过自己地址空间的虚拟地址访问内存,所以当前进程在用户态也无法访问其他进程的地址空间。
  • 最后,在内核态就能随便修改其他进程的代码段了?是的,但是你得先找到目标代码段地址空间对应的物理页,然后再映射到内核地址空间,然后新的地址空间的访问权限决定了你能如何访问这些物理页;ptrace中如下这段代码实现了这些功能,如何能执行到这些代码又是另外一个话题了;这里面牵涉到权限管理等等的问题,ptrace系统调用已经考虑的很周全了,博主会在下一篇博客里面详解ptrace系统调用的权限检查~
ptrace_request()
    case PTRACE_POKETEXT:
    case PTRACE_POKEDATA:
        return generic_ptrace_pokedata(child, addr, data); //调用__access_remote_vm
 最后的关键代码:
ret = get_user_pages(tsk, mm, addr, 1,
                write, 1, &page, &vma);
……
maddr = kmap(page);
            if (write) {
                copy_to_user_page(vma, page, addr,
                          maddr + offset, buf, bytes);
                set_page_dirty_lock(page);
            } else {
                copy_from_user_page(vma, page, addr,
                            buf, maddr + offset, bytes);
            }
            kunmap(page);

requests.exceptions.SSLError when running a py2exe generated EXE file

最近做个兴趣小项目,需要用python来get/post https请求,用到了requests模块,因为要在windows下使用,考虑到易用性和小白玩家的需求,使用py2exe生成exe。参考链接看起来很简单,统共分三步:1,安装py2exe;2,准备一个配置脚本,我们叫他build_exe.py;3,执行一条命令python build_exe.py py2exe

先把这个简单的初级版本放这里:

#usage: python build_exe.py py2exe

from distutils.core import setup
import py2exe

setup(console=[‘myqqbot.py’])

但是这里其实还是有坑的,安装py2exe要注意,并不是直接pip install py2exe就可以了,py2exe是人家python3专用的,python2的是py2exe_py2,悲剧吧,所以装包前最好还是pip search下,免得浪费时间还不知道错在哪。配置脚本貌似简单,大家应该能想到这里面其实是有很多玩法的,以至于需要一个脚本而不是直接传参。

 

生成过程很顺利,不过几秒exe文件已经躺在dist目录下了。但是双击他,console一闪而过,都不知道发生了什么。Windows就是这么变态,反正把你当小白,不给你任何错误信息。在超级难用的cmd里面用命令行调用exe后发现了问题:

requests.exceptions.SSLError: [Errno 2] No such file or directory

万能的stackoverflow告诉我们,不要慌我有药,药在这。so大夫告诉我们在exe文件执行的时候环境变量发生了改变,找不到这个pem文件了,我们需要给一个静态的pem文件路径,或者设置一个REQUESTS_CA_BUNDLE环境变量,因为不知道这个exe文件会在什么环境下执行,不能保证所在的环境中有这个pem文件,所以我们必须在打包的时候打一个pem文件进去。到此产生两个新问题:1,如何获取cacert.pem文件;2,怎么用py2exe打包,当然你肯定能想到直接放到生成的dist目录下面就可以了,但是这样是不是太low了,我们要有更elegant的方法。 Continue reading “requests.exceptions.SSLError when running a py2exe generated EXE file”

pause loop exiting & ple gap for KVM performance tunning

A system feature called Pause-Loop Exiting can cause instability and performance degradation on a virtualized system that will have a web server running in a heavily utilized guest. Turn it off by setting the ple_gap module parameter to 0 for kvm_intel.


Example: Add file kvmopts.conf to /etc/modprobe.d/ with contents:
 options kvm_intel ple_gap=0
Restart the kvm_intel module if it is already running.
 # modprobe -r kvm_intel
 # modprobe kvm_intel
or
insmod kvm-intel ple_gap=0

From Intel spec:
If the “PAUSE exiting” VM-execution control is 0 and the “PAUSE-loop exiting” VM-execution control is 1, the following treatment applies.
The processor determines the amount of time between this execution of PAUSE and the previous
execution of PAUSE at CPL 0. If this amount of time exceeds the value of the VM-execution control field
PLE_Gap, the processor considers this execution to be the first execution of PAUSE in a loop. (It also
does so for the first execution of PAUSE at CPL 0 after VM entry.)
Otherwise, the processor determines the amount of time since the most recent execution of PAUSE that
was considered to be the first in a loop. If this amount of time exceeds the value of the VM-execution
control field PLE_Window, a VM exit occurs.
For purposes of these computations, time is measured based on a counter that runs at the same rate as
the timestamp counter (TSC).

【人话总结】

永远把两次间隔超过ple_gap的pause指令其中的后一条作为第一次loop内pause,从这个第一次loop内pause开始计算次数直到超过PLE_window就触发exit
也就是间隔在ple_gap之内的连续pause指令构成了pause loop,大于PLE_window的pause loop将触发退出

Linux 内存回收机制

【基本结构】
5条lru链表:
anonymous active
anonymous inactive
file active
file inactive
unevictable
根据page的属性(是否mlock)判断是否放到unevictable list,根据pg_active和pg_active标志在active和inactive之间流动。

【在active和inactive之间流动的核心为:】

– 如果页面被认为是活跃的,则将该页的 PG_active 置位;否则,不置位。
– 当页面被访问时,检查该页的 PG_referenced 位,若未被置位,则置位之;若发现该页的 PG_referenced 已经被置位了,则意味着该页经常被访问,这时,若该页在 inactive 链表上,则置位其 PG_active 位,将其移动到 active 链表上去,并清除其 PG_referenced 位的设置;如果页面的 PG_referenced 位被置位了一段时间后,该页面没有被再次访问,那么 Linux 操作系统会清除该页面的 PG_referenced 位,因为这意味着这个页面最近这段时间都没有被访问。
– PG_referenced 位同样也可以用于页面从 active 链表移动到 inactive 链表。对于某个在 active 链表上的页面来说,其 PG_active 位被置位,如果 PG_referenced 位未被置位,给定一段时间之后,该页面如果还是没有被访问,那么该页面会被清除其 PG_active 位,挪到 inactive 链表上去。

【思考】

个人觉得,second chance回收算法在某种意义上也可以完成上面的意图,即发现某个page被pg_referenced置位了,那么把他移到链表的尾部(最新被访问),这样就不需要多个list了。但是这样就不能统计nr_active和nr_inactive了,而Linux的回收算法还会把这两个链表的长度考虑进去,做到一定程度的平衡,或者说主动的进行双向的流动,而上面说的second chance一条链的算法做不到主动去平衡。有了主动平衡,在回收的时候可以只遍历inactive链表,减少了一次性扫描太多page的可能性,一定程度上可以缓解系统性能抖动。
分了anonymous和file两种链的目的,我目前只考虑到可以在回写时区别调用不同的函数,一个写到swap,一个调用相应文件系统的回写函数
freelist保存了free的物理页,kswapd和alloc_slowpath会给他添砖加瓦,而程序的释放等是其收入的主要来源。kswapd周期性的唤醒或者被alloc_slowpath唤醒(如果分配内存的函数没能从freelist上找到所需的order的内存,那么就要悲剧的进入slowpath了,现象就是程序卡顿了,qps下降了。。。),判断当前内存(某个zone,如果有numa)是否balance,这里的balance可以简单理解为(2.6.32内核)是否低于low water mark(通过vm.min_free_kbytes的设定值计算出来的一个下限水位值),如果低于这个值,那么kswapd会shrink上面那些list,然后释放出来的page,挂到freelist里面,直到内存达到high water mark

【shrink list的顺序?】

enum lru_list {
    LRU_INACTIVE_ANON = LRU_BASE,
    LRU_ACTIVE_ANON = LRU_BASE + LRU_ACTIVE,
    LRU_INACTIVE_FILE = LRU_BASE + LRU_FILE,
    LRU_ACTIVE_FILE = LRU_BASE + LRU_FILE + LRU_ACTIVE,
    LRU_UNEVICTABLE,
    NR_LRU_LISTS
};
#define for_each_lru(lru) for (lru = 0; lru < NR_LRU_LISTS; lru++)
#define for_each_evictable_lru(lru) for (lru = 0; lru <= LRU_ACTIVE_FILE; lru++)
但是真正回收的时候,还是要给active几分面子的:
static unsigned long shrink_list(enum lru_list lru, unsigned long nr_to_scan,
                 struct lruvec *lruvec, struct scan_control *sc)
{
    if (is_active_lru(lru)) {
        if (inactive_list_is_low(lruvec, lru))
            shrink_active_list(nr_to_scan, lruvec, sc, lru);
        return 0;
    }
    return shrink_inactive_list(nr_to_scan, lruvec, sc, lru);
}

【什么样的page可以被回收 】

要看具体的配置,具体干活的函数在这里:__isolate_lru_page
基本上都可以除了unevictable不可以
如果是脏页:
Only pages without mappings or that have a
             * ->migratepage callback are possible to migrate
             * without blocking
migratepage是什么鬼:在move_to_new_page函数中找到下面一段注释
        /*
         * Most pages have a mapping and most filesystems provide a
         * migratepage callback. Anonymous pages are part of swap
         * space which also has its own migratepage callback. This
         * is the most common path for page migration.
         */

【参考文献】

在国内使用WordPress飞起来的秘籍

正如前面几篇关于本站的各种搬迁历史文章所述,现在本站使用阿里云的虚拟机;

但是,从第二次搬家到现在,一直深深困扰我的网页打开巨慢的问题今天终于解决了!!!

F**K GFW!!!

迁到阿里云上以后,立即发现WordPress的打开速度变得神奇的慢,经常把chrome浏览器都卡死了。我虽然贪图便宜买了个低配的机器,但也不至于连个WordPress博客都扛不住吧。看了cpu,内存,网络,压力都不大啊,ping下来比原来的日本vps要快多了啊,为什么呢?原来慢是网络卡可以理解,现在这个没法解释啊。

终于,我放下了作为一个搞底层优化的砖家的自尊心,百度了一下(原来是Google的,人家根本不懂中国国情,完全不能解决中国问题~),就看了一个网页,第一个方法就立即让我醍醐灌顶,看到了一个属性的敏感词“Google”:“取消谷歌Open sans字体加载”。尼玛啊~不用再看了,一定是这个原因,于是我打开chrome调试器,刷新下网页,果然。。从google获取字体timeout了。。看来我还是底层思维啊,只知道看操作系统层面的数据,要是在开始的时候就按下F12,早就不受这个罪了。

安装了“Disable Google Fonts”这个WordPress插件后,我的小站飞起来了!!!

必须再次F**K一下GFW,浪费了我宝贵的时间和精力,最主要是打击积极性啊,我不得不把更新太少的锅分一点给它~~

PAUSE指令

内核中的嵌入式汇编代码”rep;nop”会被编译为PAUSE指令,Intel Pentium4以后的CPU支持,之前的就相当于NOP。为啥rep;nop不是指循环执行nop同时递减ecx的值呢,这还真是之前困扰过我的问题,有篇文章用代码解释了这个问题-链接-。而博主自己也在虚拟机里面将cpu_relax()中的rep;nop替换为nop,前后均在host上抓取vmexit和trace kvm_exit数据,结果显示,PAUSE_INSTRUCTION造成的退出消失了。

上文中还翻译了Intel的spec,大概说了下pause指令有两种功能,一方面是可以解决memory order violation问题,另一方面可以降低循环等待的能耗。

什么是memory order violation?看这里。简单说下就是cpu的pipeline会根据执行代码的情况来预测即将执行的指令,提前将这些指令放入流水线中,达到一定的并行计算优化性能的目的,但是总会事与愿违,在spinlock代码实现中,如果不加入pause指令,很容易造成pipeline被“读入lockvar,比较lockvar是否为0”这样指令刷屏了,这就造成了即使在lockvar已经被别的CPU更新为非0值的时候,pipeline中出现了无效指令,这种情况就是memory order violation,即本应在写内存后读取内存值的动作发生在了写入之前,于是cpu就暴力的把pipeline全部flush掉,这样就造成了性能损失,因为这里是在等待一个lockvar被改变,只要及时的对这个动作做出反应就可以了。

pause指令的出现可以给cpu一个提示,这里不要给我缓存指令,等前面的执行完再看后面的,于是大大的减少了出现无效指令的可能性(此时出现这个情况的时间窗口为:读取了lockvar,但是cmp还没有执行;与之前比起来,时间窗口大大大的缩小了,之前是预读取了很多次的“读取lockvar,与0比较,跳入再一次的读取比较的分支”这三个动作,最后一个跳转也是预测的)。

节能的效果更好理解,其实这里就是在原地踏步,不是要求一定时间内踏的次数多,而是要对出现的情况及时处理。所以加入pause可以让处理更及时,而且不必把能量浪费在多出来的无意义的指令上。

WordPress忘记登录密码怎么办?

好不容易想发个文章,发现chrome把我的WordPress登录密码丢了,天呐!作为浏览器你都不知道我哪知道?我那么多的账号。。好吧,我太懒了,又好长时间不更新了。

核心方法:
mysql> update wp_users set user_pass=”xxxx” where id=1;

1)xxxx是密码的md5值,需要生成一下,有个网站很方便:http://www.md5decrypt.org/
2)id=1换成你要改密码的那个user的ID,在MySQL里面查:select * from wp_users;

参考链接:

How to reset WordPress admin/users password from Linux command line?

第二次搬家即备案和换域名

在阿里云备案很简单,买个.com域名,阿里云注册一堆信息,发来一张阿里云墙纸,拍张照发过去,接几个电话。

域名解析换一下,鼠标点点就OK了。

mysql备份恢复到新的server,然后替换数据库中所有的原域名。

下面是替换全站的所有设置信息和内容中的旧域名的方法:

UPDATE wp_options SET option_value = replace(option_value, ‘www.luoben.xyz’,’www.luo666.com’) ;
UPDATE wp_options SET comment_author_url = replace(comment_author_url, ‘www.luoben.xyz’, ‘www.luo666.com’) ;
UPDATE wp_comments SET comment_content = replace(comment_content, ‘www.luoben.xyz’, ‘www.luo666.com’) ;
UPDATE wp_posts SET post_content = replace( post_content, ‘www.luoben.xyz’,’www.luo666.com’) ;