唐僧被吃了

唐僧被吃了。

本来不应该发生这样的事的。

正常来说,悟空会随时在师傅身边保护。要逃过悟空的眼睛,几无可能。因此唐僧的安全可以说是固若金汤。但是事有凑巧,今天是悟空回天庭报道的日子。由于悟空当年大闹天宫,被压在五行山下。今日戴罪立功,属于假释。所以按照天庭假释管理办法,需要每个月向天庭报道一次。当然,正常来说这种事情也就是走个过场。天上一日地上一年,每月报道一次是按地上时间计算的,所以在天上就是每个时辰出现一次,日夜不休。天庭监狱管理委员会的工作人员也受不了这个繁琐,所以文书手续,验明正身一概抽查。正常而言只要猴头出现,就算过关。

但是今日,玉帝闲来无事,要去隔壁礼部视察。礼部主事和刑部尚书关系交好,于是偷偷通知了刑部尚书。万一玉帝没事干,出了礼部进刑部,那就是天大的麻烦。所以各种见不得光的事情都要收起来。因此悟空这几个月的报道格外麻烦。

为此,悟空特意驾筋斗云前后看了一圈。附近地界太平,没有什么妖孽。又召出土地来问过,再三确认安全。于是向二师兄三师兄好好叮嘱了一番,这才上天庭报道去的。

然后唐僧就死了。

如来很生气,后果很严重。悟空队还没排到,就被监狱管理委员会的人扣下。西天联合天庭,成立天庭取经事故联合调查委员会,由迦叶尊者任首席调查官,主持调查工作。太白金星出任首席行政官,代表天庭协调双方工作。

委员会成立后,首先对事情的经过进行了初步的调查,基本排除了悟空伙同外人作案的可能性。于是委托太白金星对悟空进行谈话,说服其配合委员会的工作。在这次谈话中,悟空才初次知道师父去世的细节。

据天庭取经事故联合调查委员会的初步查证,事故是这样发生的。唐僧师徒行路到一半,看到个牌子,上面写着,大雷音寺,左转向前,还有28公里。于是唐僧师徒毫不怀疑的左转。结果前方并不是大雷音寺,而是小雷音寺。唐僧师徒住下后,唐僧在沐浴时不慎撞到头,昏倒在池内。童子问水是否够热无人回应,误以为客人喜欢热水。于是添足柴火,三个时辰过去,唐僧师父被煮成一锅老汤。。。

太白金星话还没说完,就被悟空飞起一脚踢倒。还没站起身,就被悟空楸住领子:老官,你扯的什么鬼话。哪里有人会呆呆被烧一点动静都没有的,我师父又不是木头。这种鬼话就是骗童子都不够,你真当俺老孙呆子不成!

太白金星连忙求饶:大圣,大圣,听我一言。当初调查委员会的人也是不信,可是小雷音寺的人说,西方有科卡罗斯煮死米诺陶斯,那米诺陶斯又不是呆子。为什么唐师傅的事就不可能是意外呢?

悟空冷笑一声:就算是意外好了。他们装成大雷音寺,骗我师父入住,这才能不动声色煮死我师父。这冒充大雷音寺的罪过,总不是意外了吧。

太白金星又说:这个我们也调查过了。对方拿出一份西天颁发的“关于鼓励信众自行传教的规定”,其中第一百七十三条第五款规定,为了传播西天教义,允许采取各种形式。因此小雷音寺的信众们就采用模仿大雷音寺的方式,向民众宣传大雷音寺的尊严。

大圣顿时哑口:这种事难道教务办不管么?

太白金星道:教务办哪里有空一个个检查信众自行传教的细节,最多也就是出了岔子找出首恶而已。实在乱子搞大了,了不起再出一个“关于禁止信众自行传教的规定”也就是了。

悟空咬咬牙,再道:就算小雷音寺无事,那乱插路标,诱我师父前去之人呢?

太白金星拨开悟空手指:大圣,小雷音寺的人对我们都拿的出“关于鼓励信众自行传教的规定”,对着人家自然也是拿出规定,说为了弘法,故此需要改标线路就是了。

悟空双手抱胸坐下,翘起二郎腿冷笑:天下哪里有为了弘法,把正法指向邪路的道理。必是此人见钱眼开,没细细核对文件之故。

太白金星道:悟空,你又何必固执。他又不是吃你师父的首恶,你何必和他过不去。要说细细核对文件,你前几次的假释报道,似乎也是核对不全吧。

悟空轻叹口气:师父也许呆,俺老孙却不傻。小雷音寺敢仿大雷音寺而无事,显然是背后有人不希望我师徒前去西天。因此调查报告里鬼话连篇,就是不敢指摘背后之人的不是。我待罪之身,哪里能左右这些。俺老孙唯一能左右的,便是此为虎作伥之人。若不是他从中作梗,哪里来这许多事。周围乡邻,难免也为其所骗,拜错菩萨。难道你认为他无罪?

太白金星微微一笑:非也非也,大圣你这么想是再好不过。

悟空微微一愣:怎讲?

太白金星道:既然小雷音寺众人无罪,这桩事总也需要个了结。你是观音菩萨保举,若说是你玩忽职守,大家面上不好看。西天的意思是,此人擅做主张,引诱唐僧师父到小雷音寺,以至招待不周,发生意外。此人负有不可推卸的责任。大圣你再做污点证人,说他当初也为你指路,并无什么不妥。以至于未曾察觉。

悟空怒道:合着你们就是找我坑替罪羊来了?

太白金星道:大圣,大圣。你不刚刚还主张要严惩此人?如今随了你心意,你又要怎样?

悟空呆若木鸡,不知如何自处。

PS:其实昨天就写完了,拿给霍叔叔看。霍叔叔说,你这类比太绕了。我说好,我想办法改改。结果还没等我改,取经事故联合调查委员会已经宣布了调查结果。再等下去怕是连西游记都要被查禁。所以赶紧出一版,大家凑合看看吧。

三亚潜水体验

最近去三亚玩了,我就说潜水吧。

水况

潜水地点是在分界州,水不算太好。水下景点一般在深度5-15米范围内,有两艘沉船,一个飞机残骸。小沉船离岸比较近比较浅,水深不超过10米。大沉船需要再游一刻钟,深度15-18米左右。飞机残骸也差不多深度,不过只看到一点。水下危险生物包括狮子鱼石头鱼棘冠海星(魔鬼海星),水母。这次潜导在水下就被水母蜇了,被蛰的还很神奇。他下水就戴了面镜,升上水面就开始痛了,还被蛰在眼皮上。到底水母是怎么进去的就鬼知道了。不过问题也不严重,痛了一会就没事了。

能见度分别比较大。在10米左右有一个明显的分界面。在分界面上,能见度大约是10米左右,下面只有3-5米。从上面明显能看到下面像一潭池塘一样。水底温度21-22度。我去的时候是四月上旬,所以只能代表这个时间点的情况。据说6月前后的时候能见度会好很多,水温也会比较高。亚龙湾和蜈支州的情况据说要好点,不过从我看到的水色来说,估计好不了太多。

洋流情况还好,0.5m/s以下,一般都不构成问题。那天浪在0.5m左右,水面上有点晕。周围潜水环境还行,只是偶尔有人炸鱼,可能有巨响。另外水面上有摩托艇在开,和潜导分开的话,没有SMB上浮会比较危险。

潜店

这次潜水是走的中仁潜水的持证fun dive,价格是880两支气瓶。价格包括接送,上岛费用,一顿午饭。还包括了海豚表演门票。但是fun dive来说,根本没时间去看。

BCD,fin,regulator是潜店提供的,他们问了我的身高体重来配fin和防寒衣。不过防寒衣是岛上提供的。我的体型比较大,所以穿了他们最大的防寒服。5mm的,比较适合水况,也很新,估计是穿的人不多的缘故。本来还问了我是否有度数,不过我的面镜和呼吸管是自带的,所以用不到。我还自带了3mm的手套,考虑到这里的水下危险生物,这个举措其实非常明智。

岛上有免费的更衣和冲凉,但是寄包要20元。气瓶是中潜自己打的,一般都超过200bar。但是配重是公用的,比较烂。

我们潜FD是岸潜,水面游动100-200米。DSD有一个平台,跳下去就行。平台那里比较浅,据说在5米左右。

三亚潜水

除了分界州外,我还去了一趟亚龙湾。在那里,我碰上了三亚名产——体验潜水。

下车之后我就看到有人在做潜水培训。仔细看了一下,是一个潜水体验旅游的报名点。名义价格是400左右。我仔细观察了一下,整个点没有任何标志。没有PADI或者CMAS的标。

在我吃饭的时候有人过来拉生意,鼓动我们潜水。我直接说我是PADI的持证潜水员,然后他就消失了。

不做任何评论,大家自己分析。

注意,在三亚海滩上是有打着PADI标志的潜店的,在PADI上也可以查到这家。不要以为我说的是他们。我其实在他们店里休息过一会,听了他们和客户的一些对话,觉得他们做生意还基本不过分吧。

潜水的一些简单解说

潜水的乐趣

潜水好不好玩?不好说。这得看你是不是喜欢潜水。不同的人在潜水中获得不同的乐趣。有人喜欢看鱼看珊瑚看沉船,有人喜欢水下漂浮的感觉,有人甚至只是泡妹子/帅哥。是的,潜水者里帅哥/美女的比例极高。因为潜水很要求体力,所以大部分人身材都很好,像我这种胖子绝对是少数(不过在学校里我看到了另外一个胖子,比我还夸张,衣服都是自带的)。我甚至看到一个学着学着潜水泡上了教练的妹子。。。

是否适合潜水

潜水的一般性要求是10岁以上,没有心血管疾病,癫痫什么的。细节可以去PADI网站上看一下。通常对于休闲潜水而言,大部分人都应该是没问题的。这里只说几个上面没有的问题。

不会游泳能不能潜水?

咳咳,我就不会游泳。

潜水对游泳的要求是,能在水面上游动200米,或者在水面停留10分钟(我记得是这两个值)。这基本和会游泳没什么区别——除了通过条件。考核这项的时候,是允许你穿着防寒衣在海水里考的。这等于让你穿件救生衣问你会不会游泳一样,我想大多数旱鸭子的水性还不至于糟糕到这种地步。糟糕到这种地步的,坐船都要额外买保险了。

但是会游泳还是非常有帮助的。如果会游泳的话,在水下移动的时候会很有优势。所以建议还是去学一下游泳。当然,你可以学以潜水为目标的游泳——主要就是不用换气。不求游多少距离,没气了站起来喘完了再游也行。

潜水的另一个要求是胆大心细,遇事冷静。在水下碰到状况,很多都只能靠自己。所以胆子要大,但是遇事要冷静。

当然,冷静谁也说不好。有的人平时也挺冷静,碰到大事了就突然反应不过来。大多数人都是这样,也不用不好意思。至于胆量,有个很简单的测试。找一个游泳池(当然,是淡水),3米以上,脚够不到底的地方。不穿救生衣,从岸上往下跳(注意,很多泳池禁止你这么做,请首先咨询管理员取得许可),跳下去之后游回岸边。捏着鼻子插筷子也好,摒气也好,随便你。看你的胆子和游泳技巧是否能够做到。如果做不到,例如不敢跳,或者跳下去根本不会游上来,那就不要费劲了。你跳下去不是潜水,是去找死的。

技术解说

携带呼吸装置潜水其实只有一个要点,就是呼吸。除此之外的东西都只能算是技术,学了肯定能会的那种。例如怎么组装备,规范动作怎么做什么的。智商没什么太大问题都很容易。

可能很多人会首先说耳朵的问题。耳压平衡是潜水基础中的基础,一上来就会讲的。在水下做了平衡就不应该耳朵痛了。像三亚名产,不做平衡告知就丢下水,10分钟后耳朵痛就升水,那是道德问题,不是技术问题。如果学了耳压平衡但是做不到,那平时坐飞机都应该有问题,你需要检查内耳疾病。

呼吸之所以重要,是因为呼吸关系到浮力。会游泳的应该有体会,浮在水面的时候,吐气就会下沉。潜水的时候也是一样,吸气上浮,吐气下沉。听起来很简单,但是水里需要呼吸不停,而且由于游动,呼吸量还不小。这种情况下如何保持稳定,就是一个非常有技巧的事情了。技术上说,这叫中性浮力。好的潜水员甚至可以靠呼吸停留在沙滩上十公分处,既不接触,也不浮上去。

潜水的时候,身上的气瓶是负浮力。防寒衣虽然是正浮力,但是不足以平衡气瓶。如果只穿这两件的话,就必须不断踢水,停止踢水就会下沉。所以身上会背一个充气背心,来抵消气瓶的浮力。这样通过向背心内充放气,可以将全身的浮力和重力抵消到1公斤以内。人的一次呼吸一般潮气量在3000左右,换算成浮力有三公斤左右的波动,这样就可以通过呼吸来控制上升下降。一般操作上还会额外配一些配重,然后通过背心的浮力抵消配重。这样一方面在下潜的时候允许通过放气变的更重,可以有更大的余地。另一方面,在遇到紧急情况的时候,也可以通过快速脱下配重浮出水面(警告,这样很危险,容易产生减压病或肺部过度扩张)。

浮力的难点在于,呼吸对浮力的影响是瞬时的,但是你很难感知现在是正浮力还是负浮力。你能发现的只有浮力对深度的影响,现在是往上飘了还是往下沉了。这就像顶杆子一样。你不能感觉到力,只能感觉到位置变化,不断调控。而呼吸要影响到深度,至少需要1-2秒的延迟。而且等你发现的时候,往往像火箭一样,停不下来了。好比上浮,吸一点气,我擦没反应啊。再吸一点?哎呦沃草升天啦。赶快吐气,我擦快停下,快TM升水了。好了好了。。。等等,我擦怎么变砖头了?再来。。。等你折腾一会,教练会让你看一眼压力。靠,没气了,升水吧。

理论上说,要找保持悬浮,需要找准平衡气量,在这个范围内小幅呼吸。但是在不同深度下,充气背心的浮力是会发生变化的。潜水到后面,气瓶里空气不足,也会产生额外的浮力。你需要不停用经验发现现在的平衡气量是多少,围绕着小口呼吸,甚至有的时候需要充放气来重新平衡一下。

中性浮力是很多东西的基础。例如在水下摄影,如果浮力一塌糊涂,一呼吸相机都抖成中风了,潜水又禁止憋气。那还照个P啊。从珊瑚上游过也是,如果浮力控制不好,要么离很远,要么你就会直接在珊瑚上砸出一个人型的坑。

PADI的技术路线

PADI的入门课程从小孩开始(骗钱从娃娃抓起啊),我们这里只说大人的。

最低的是SCUBA Diver执照。一次体验潜水,然后做泳池课程和基础训练,再潜两次就能拿到。这个执照能潜12米深度。

大部分人应该没听说过SCUBA Diver执照。因为只要再多两潜,几乎不需要额外课程,就可以拿到OpenWater执照(简称OW)。大部分人会直接潜水五次,拿OW执照。潜水深度从12米提高到18米,这样就可以去大部分地方潜水玩了。

OW再往上是Advenced OpenWater(简称AOW)。AOW在拿照方式上和OW有本质性区别。OW是固定课程,你想不想,五节课都要过才能拿证。AOW是一堆专长,两项必修的,其他再选三项,就能通过。总计来说需要潜水五次,两次超过18米深度。完成AOW执照后,可以潜30米深度。

上面这些深度什么概念呢?例如某次我们潜的Greenrock,一块大石头,三个顶部。最高的地方7米左右,最低30米没到(印象里的资料,可能不一定准)。SCUBA Diver的话就只能看三个顶部。OW的话就能绕着大石头一圈看岩壁和缓坡。AOW的话可以在石头底下绕一圈——正好有个班戟鱼的窝。所以很多点其实大部分执照都能下(甚至包括体验潜水),但是不一样的执照看到的东西会有差。除非你打算只去深度非常小的地方,否则执照当然是越深越好玩。当然,以休闲潜水来说,40米的深度还有很多地方去不了。但是安全起见(也是为钱包的安全),暂时就这样满足了吧。

然后就是AOW的一堆专长。例如我修了一个鱼类辨识(看着很扯淡,其实就是看鱼玩),一个深潜,一个水底导航,一个沉船,一个顶尖中性浮力。其实大同小异(骗钱也是不手软哦)。除了告诉你一些技巧外,主要就是在不同环境下潜水。等潜水次数多了,自然就熟练和有信心了。

AOW和AOW以下的执照,加大部分的专长执照,其实都不难拿。有钱,有闲,也就差不多了。甚至都不必须考。例如我需要去夜潜了,现场找潜店把钱交掉,题做掉。然后就可以带着去了,回来就拿到证了。无非也就是哪里拿的证和多少钱的问题。

注意我说大部分。AOW有个专长叫潜水摄影,是问的最多的专长。尴尬的是,这货的难度非常高,我不知道为什么这货会进AOW专长课的。考这玩意一般需要学上几个礼拜,比DM都简单不了多少。当然,买的下潜水相机的人而言,倒是不会缺钱的。

其中还有一个特别的执照,Nitrox,高氧潜水。高氧主要是用于长时间潜水,减少残氮量的。这玩意不是长时间潜水几乎不会用到。高氧的特别之处就在于,一,高氧潜水有额外风险,二,你可以拿高氧去潜深潜,从而一次性多拿一张执照。

特别重复说明一点,并不是说氧气含量高了就能潜的深。恰恰相反,氧气浓度越高,潜水深度越低。纯氧在水下六米处吸了就有很大可能会挂掉,反倒是EANx32(氧含量32%)的氧气能允许你潜深33米。高氧的唯一目地就是减少残氮量,从而增加你的免停留潜水时间。这方面详细情况,我会写一篇关于高压空气的话题。

要不要修哦?如果你可能会单次重复潜水,次数很多的,深度很深的,可以考虑。因为气比免停留时间还长。如果是一天两潜,深度又不高的。免停留时间本身就够,你要高氧干什么呢?急救?

AOW再往上,潜到20左右,可以去出一张RESCUE的证。这张还要配修EFR的,贵的要死,不过基本没啥用。RESCUE是你能救别人,不是别人能救你。但是尴尬的是,又不是因为你有RESCUE执照就能带人潜水了——恰恰相反,你自己潜水还是需要人带的。而万一出了问题,正常来说带你的那个人急救资格都比你高(也肯定比你熟练)。那这张证能干嘛呢?在带你的那个人不在的场合下——例如不在潜水的时候——救人。

你妹,不如说让我TM学游泳救人算了,还用的上点。

潜够40,可以开始考DiveMaster(潜水长)的执照了。这个执照非常难,一般都要70潜以上才考出来。因为AOW和DM的差别就是业余和专业的差别了。在过了DM之后,你潜水就不需要人带,可以自己约潜伴了。

我暂时还没搞明白,DM是不是能带AOW或者OW去潜,还是AOW或者OW必须由OWSI带。反正我碰到所有的DM都拿到了OWSI。

DM再往下是专业线了,我记得不是很清楚,自己看吧。反正拿到这个证的,课程都远比这点发展线路复杂了。

潜点选择,交通和成本

每个潜点都有一些特点。例如珊瑚(珊瑚周围往往有大量热带鱼),石缝里的鳐鱼,海鳗,天然形成的洞穴,沉船等等。这些特点往往会对应不同深度。有的时候证不够就没什么必要去某个潜点——好比koh tao的Sattaukt这个点。沉船的位置在水下24-30米,没有AOW的证根本去不了,只能在上面远远看着。还去干嘛呢?不如去隔壁的Whiterock钻洞。还有一些点,会在特定的时间形成特色——例如鱼类季节性孵化和成长的时候。这些也需要提前搞明白。

所以要到一个潜点前,需要查潜点地图,搞明白每个潜点的特色,需要什么证书,并且提前约好潜店和潜伴。不同潜店不同时间会去不同的点,有的潜店甚至需要提前预约才行,否则当前是没有船去的。至于约朋友——要知道,潜水行程意外太多,凑不齐人不奇怪,凑齐了才是意外。要潜店约一堆人容易,自己约朋友就纯看运气了。就算平安无事到了地方,万一碰到感冒就只能扫兴而归了(我不大建议吃通鼻子的药去潜水)。所以要珍惜每个和你一起潜水的朋友,能碰上是多大的缘分啊。

每个潜点也会受到交通方式的影响。近海的潜点可以船潜或者从岸上下水,但是稍微远一点的,十有八九都要船潜,甚至需要船宿。这时候需要考虑自己是否受得了船宿的晃动和晕船。

还有,来返潜点的交通方式往往用飞机。但是潜水18小时内是禁止坐飞机的。所以假期短的话,会耗费大量时间在潜水以外的时间上。这会使潜水成本大幅上升。建议能准备一些不潜水也能玩的地方来填充时间段,同时尽量准备长一些的假期。

装备

作为入门,一般你不需要买任何装备。大部分潜店都会租装备给你。大多数情况下,自带装备的运输费用(和劳动量)会比租装备还贵。唯一例外的是有戴眼镜的人的潜水面镜。潜水不能戴眼镜(废话)。为了安全起见,也不建议戴隐形(做过全面镜脱着的应该知道为什么)。所以建议你买一副潜水镜(注意,要罩住鼻子),然后凭你现在的眼镜度数去配光学玻璃装上去。

如果体型特别巨大(脸好疼),可以考虑买自己的防寒衣或者全身式潜水衣。以免你想借却借不到的尴尬。水鞋没啥必要,反正一般常潜的脚底都应该够厚了,不常潜的买了你也用不上。

如果经常潜水,例如一年五潜以上,可以考虑买一支自己的潜水电脑表。入门的也就千把块。自己的潜水表可以记录自己潜水的精确数据,潜店的装备里一般不会包含电脑表(除非租高氧气瓶),而压力表上连的那个二货往往功能有限不说(例如无法设定高氧),你也不能连着调节器一起拿走啊——那还不如直接买电脑表呢。

值得初学者考虑的装备还有能在水下写字的板子和笔,水下的哨子(或其他声响设备),潜水手电。水下不能说话,通讯全靠手势。万一碰到手势搞不定的问题,不想升水只有用白板了。不过这种例子一般不多见,反正我潜了十多回就碰到一次,还是在水下考鱼类辨识。哨子非常有用,方便通知别人让他们看你手势。但是这玩意常规也不大用,一般只有DM叫人的时候用。如果有上过夜潜课的话,潜水手电也可以考虑入一个,大不了平时当普通手电用。

一般好像很少有人会自带BCD的。我见过一起潜水刷瓶子打算考DM的一个妹子自己有一个背飞的BCD,但是实话说没感觉哪里必要。先不说价格,每次潜水回去的时候把BCD洗好收起来都是个大麻烦,更不提带来带去的重量。调节器也没见有人自备的,一般拿船上的就行。自己的还要定期找人检修维护,万一哪个岩石卡一下又要心疼半天。倒是O-ring可以自备几个,以免气瓶还在,O-ring没了。

倒是很多不在装备清单里的东西很值得备一个。例如防水包。在船潜的时候,潜水日志啦,眼镜什么的要找地方放,还要防水。有个防水包会非常方便,还能放放衣服。还有,如果晕船的话,最好准备一些不嗜睡的晕船药。有的潜店会准备,但是有的潜店就不会管这个了。别以为自己不会晕船,你试试多潜几次再说。

一个有趣的问题

前面给公司出了一个有趣的问题,似乎没采用。所以现在放出来大家看着玩玩。

以下代码在python2中适用。python3请看尾部注释。

import os, time
data = range(10000000)
pid = os.fork()
if pid < 0:
    print 'error:', pid
    os.exit(pid)
if pid > 0:
    os.wait()
    os.exit(0)
sum(data)

在第2行执行的前后,使用ps和free观察内存使用情况,可以看到进程使用了320M内存,系统被占用了320M内存。在3行执行后再观察,有两个进程分别占用320M内存,系统总计被占用了320M内存。 问1,为什么两个进程分别占用320M左右内存,系统总计占用数并没有翻倍?这种现象叫做什么?

在10行执行后再观察,有两个进程分别占用320M内存,系统总计被占用了560M内存。 问2,为什么sum增加了系统内存占用,解释其开销。

问3,推测出python整数对象长度和当前CPU字长。(python自身的内存开销忽略)

答1. 这种现象叫做COW,copy on write。在fork后,两个进程会共享内存表项,一致的部分会仅使用一个页面。

答2. 每个进程的内存占用都没有上升,但总内存占用量上升了,这必然是发生了页面写时复制的结果。页面写时复制必须写入内存,因此推测python使用引用计数 手段控制对象生命周期。当sum时,每个对象都要被读取。在读取前,系统会增加其引用计数。在这个过程中会发生页面写时复制,导致系统内存占用上升。

注:内存复制必须发生在写时刻,说读取导致内存复制的统统不得分。答出引用计数四个字即可得全分。

答3. 在写时复制时,数字对象会增加引用计数,而数组对象不会(准确的说,数组对象本身引用计数会增加,但只有一页会发生复制)。发生复制增加的内存有 240M,因此整数对象长度24字节。未倍增内存有80M。对象有10M个。因此推断指针长度为8字节,当前CPU字长64位。

注:python3里有几个不同。首先,range返回了一个生成器,所以需要改为data = list(range(10000000))。其次,python3中,int长度为28,pointer长度为9。因此下面的数据会需要调整,而且并不很容易解释。

一次升级故障的排查

问题描述

前两天,我收到了USN-2900-1通知,glibc上有个严重漏洞,可导致DoS或(可能性的)执行任意代码。这个USN对应的CVE是CVE-2015-7547,我相信很多人应该听说过这个漏洞,或者应该已经修复了。我也不例外,很快的推进修复了这个漏洞。收到消息6-7小时后,已经看到了POC

OK,我们本次不是讨论这个漏洞本身的。在漏洞修复后,有一台设备报错,无法安装程序,wget都无法执行。很有可能是漏洞修补补丁导致的。由于这个机器很关键,目前很难迁移,所以我需要找到原因。另一方面说,如果无法明确原因,已经执行补丁的机器群将无法确定可靠性。因此无论如何,必须要研究一下原因。

出问题的机器是一台ubuntu12.04,修补的版本号为2.15-0ubuntu10.13。

处理过程

首先,CVE的说明表明这个漏洞来自于getaddrinfo调用。因此首先确定问题和这个调用的关系。使用python,import socket,然后执行任意一个socket.getaddrinfo。python崩溃了。这就说明问题很可能来自本次修补。当然,同时的测试中表明wget也会崩溃,但是dig没事。所以dig很可能并没有产生getaddrinfo调用。

而后,我使用strace和tcpdump追踪了一下程序。tcpdump表明一切正常,并没有人正在攻击系统,因而可以排除修补不妥当加正在被攻击导致的崩溃。strace中断在ioctl调用后,除了进一步明确当时正在进行DNS查询外,并没有给出太多有效的信息。

常规来说,下一步应该是gdb。但是由于apt无法执行,所以gdb装不上去。因此我跳过gdb,先翻了一下USN-2900-1补丁的细节。这个补丁里包含了所有ubuntu自己打上去的补丁,因而有点大。但是仔细看之后可以分离出CVE-2015-7547补丁的位置:debian/patches/any/CVE-2015-7547.diff(还有pre1和pre2)。仔细阅读修补代码,尽管对逻辑并非十分清楚,但是并没有看到什么奇怪的错误。结合剩下的机器并没有问题,我基本认为这个补丁是没问题的。

后面很幸运的,向才发现虽然apt-get update不行,但是apt-get install却没问题。不知什么原因,总之我们有了一个能用的gdb系统。通过gdb,我确定了出问题的代码行号。我本来想省点事,从补丁上直接读出出问题的行(eglibc-2.15/resolv/res_send.c:1303)。但是很遗憾,出问题的行本身似乎没有什么问题。(*ansp2_malloced = 1;)虽然可以看出是ansp2_malloced跑飞导致了SEGFAULT,但是并没有提示为什么。所以还是得需要完整的源码。

然后就是debian包维护的基本功夫。首先用apt-get source libc6下载源码。再用dpkg-source -x解开文件。进入目录里,用quilt push -a应用全部补丁(细节看这里)。这样就得到了和线上一致的完整源码。我本来想将整个过程在目标机上完成,但是目标机上却无法安装quilt(根本找不到这个包,由于apt-get update无法执行,我也无法修正这个问题)。所以最后源码的补丁是在我本地的workstation完成的。

无论如何,我按照gdb的bt输出,仔细核对了出问题的代码。问题确实是出在了ansp2_malloced,这个值为0。但是这个值是逐层传递的参数,传递的最后几行表明这个值都是0,而在前面,这个值是有有效值的。可是吊诡的是,在有效变为无效的过程中,指针并没有修改过。

这个值是在__libc_res_nsearch(resolv/res_query.c:331)里发生变化的。在这个函数里,这个指针叫做answerp2_malloced。根据gdb的bt,在函数入口上,这个数值不为0。但是到了第421行,调用__libc_res_nquerydomain的时候,就变为了0。而从函数入口开始按照顺序搜索answerp2_malloced,都是对指针的值的修改,并没有对指针本身修改。函数也是传递指针,而不是双重指针。也就是说,answerp2_malloced在一个并不可能改变的代码段中被改变了。

读到这里的朋友,有兴趣的话可以先不要往下看,先猜猜原因。这个神秘的现象直到我看到结果,才反推出来为什么。

原因

我在这里被卡了很久。后来在无聊中,往下看了一下bt。留心到其中某个函数并没有调试信息,因为这个函数所在的so文件位置不对。仔细一看,这是一个glibc的库,但是却在/usr/local/lib下面。

我X。是哪个孙子把系统库的部分拷贝到了其他位置,还改变了LD路径。。。拿一个版本的glibc和另一个版本的glibc库混用,不出问题才见鬼咧。把这个so文件改名后,问题立刻解决。

复盘

事后根据复盘。这个参数其实是为了修补问题,新加的。原本__libc_res_nquerydomain函数并没有这个参数。然后为啥能跑?这涉及到linux下C的入栈顺序。这里不讲细节,如果你有兴趣,先看这个。再往下看复盘。

根据调用规则,调用者首先会对现场压栈。他会将需要保留的寄存器入栈,以防子程序改变他们。然后他会根据C规则或pascal规则入栈参数。最后call指令会将当前IP入栈,并且转跳到指定地址上。以这里的情况看,应该是默认的C规则。

C规则的好处在于被调用者获得的是栈顶相对位置,其余参数向栈底依次展开(注意下面用的全部是栈底/栈顶,由于对口,实际内存分布一般是反过来的)。使用BP+8,BP+12这种规则来访问。因此调用者可以传递变长参数。无论你在实际参数之前入栈了多少个数据,只要调用者最后记得把这些数据出栈,就不会对执行构成影响。而pascal规则则不然,为了获得参数位置,被调用者必须知道传入了多少个参数。例如访问第一个参数,就需要用BP+4*N+4来访问(当然在编译时这个数字会被静态的算出来)。如果你在实际参数之前入栈了数据,那么被调用者就需要用-1这种方法去访问这些数据了。而如果在实际参数之后入栈数据,整个参数位置都会错乱掉。因此pascal规则一般被认为是不能传递可变参数的。

但是C规则的这个优势,在这里变成了问题。调用者的代码还是打补丁之前的版本,而被调用者的代码则是打补丁之后的了。因此__libc_res_nsearch将参数入栈前入栈的最后一个元素认做了最后一个参数。这个元素,可能是需要保存的现场,也可能是局部变量。

如果__libc_res_nsearch将现场当作了最后一个元素的话,将无法解释这个值为什么在后面发生了变化——被保存的现场一般来说是用于未来的恢复的,他们不应当发生变化。而如果是局部变量则相反,局部变量的指针经常被当作参数传递给子函数——这也是经典的C多值返回方法。

如果__libc_res_nsearch确实接受了局部变量的地址作为参数,他可能会向这个地址写入任何东西——例如0。这就造成了这个地址在调用时有值,但是在使用时值被清零,又找不到任何地方修改这个值的缘故。

当然,这里也有很多疑惑。例如局部变量顶上一般会保存被调用者需要保存的现场。无论如何,参数和局部变量之间一点现场都不隔,是件很奇怪的事情。而现场一般是不变的。对于这点,我没时间去反向源码并分析栈的实际情况,谁知道可以告诉我。

这是一个调用者(caller)的原型认知比被调用者(callee)少一个参数的结果。如果事实反过来,调用者的原型认知比被调用者对一个参数,那么代码执行将不会有任何麻烦。

事后分析

这个故障最主要的原因是有人将系统库复制到了/usr/local,并修改了LD顺序。

glibc确实是一个几乎没有改动的库,但是这不表示他不会改动。根据我这里的记录,在过去的一年半时间里,他改变了八次。有趣的是,最早一次改动也是因为getaddrinfo的漏洞做修补(USN-2306-1)。也许是运气好,前几次改动中并没有调整参数,或者对参数的意义做变更。因此老代码和新代码的混合调用并没有出现问题。然而本次修补就过不去了。

这并不是ABI的错——ABI承诺的是“向外暴露接口”,而libc中的内部互相访问显然不在其中——谁会承诺自己内部结构的ABI兼容性呢?那会让稍微复杂点的重构都无法进行。

根本的问题在于,为什么有将glibc中的一部分提取出来,放在/usr/local中固化的需求呢?(或者对glibc的实现做调整)而且从操作上,即使我们需要对glibc动手脚,最好将整个编译结果放在/usr/local中,完整替换全部的glibc库。当然,这个行为会使得glibc的修补补丁彻底失效。根据运行时错误好过逻辑错误的理论,这是一件比崩溃更糟糕的事。

更糟糕的问题并不在glibc上,而是这台机器的维护状态,还好这是一台开发用机,而不是在线机器。如果有人知道这台机器的LD被做过手脚,应该很容易能够想到这个问题的原因。但是在复盘中我询问多个人,都不知道这台机器被如此设定的理由,甚至没人知道如何维护这台机器。实际上这台机器的维护曾多次易手,其中有些人根本已经离职。即使尚未离职,也未必记得自己到底做过哪些事。而我也找不到任何相关文档表明这台机器被如何的维护了。这也是为什么这台机器不好迁移的原因——在机器上有太多明的暗的诡异的workthrough,要将其迁移到另一台机器上是件耗费人工的事。我维护的很多机器(帮朋友维护)也处于类似的状态。维护时间太长,经手人多次转手,上面很多设定完全黑化,要迁移需要付出相当代价,等等。。。

docker能解决这个问题么?

不好说,这个问题有点复杂。

从本质上说,docker解决不了这个问题。因为一个workthrough,存在于整个系统中,还是存在于一片dockerfile的海洋中,其实没太大区别。如果我会跑上去就看dockerfile,然后从一大堆过程中一眼看出问题,那我也会在这次解决中先去看LD设定。最低限度,看bt的时候根本不用参考源码,往下看两行就知道原因了。而即便知道原因,这句cp存在于里面的原因仍然未知。我不知道为什么会把这个文件cp到/usr/local,无论是机器上实际存在的复制,还是dockerfile中的cp语句。最后实际有帮助的,还是在这句cp上注释的信息,或者机器上留存的维护文档。

但是docker是有帮助的。首先使用了docker起码好迁移了。当然,这不是他最大的帮助。

docker最大的帮助帮助并不是来自于能够帮助我简化寻找流程,或者知道原因。而是来自于减小系统规模,甚至可以将系统规模降低到需要的最小规模。在一个复杂系统中,寻找一个workthrough,或者知道为什么是困难的。但是在简洁系统就容易很多,非常多。

当然,将复杂系统拆分为简洁系统是有代价的,并不那么容易的。在这点上,我比较信奉熵增原则。在增大系统规模的时候,系统的熵永远是增加的。如果要降低熵,就需要对这个系统做功,无论从哪个方面。随着手段不同,做功只有大小差别,而没有一颗“一次解决”的银弹。从这个意义上说,docker并不能解决这个问题,他只是能降低你需要做的功。

而如果对某个系统做功并不产生实际效用(而只是为了将来可能性的维护便利)时,这部分开销就可能被砍掉。反正解决了问题并保持一段时间,相关人员可能就不再继续维护,或者高升,或者离职,或者根本转行。于是系统中(包括代码和维护)会不断产生各种workthrough,“将来要修”的承诺,和随着不断的人员变更不再有人记得的暗创。最后系统就会变成遗留系统,没人知道为什么,没人敢动,也不敢停。静静的在那里,吞噬一批又一批IT从业人员的青春,同时也产生更大的熵。