因缘混沌发机缄,此夜重相见。
凭今回首忆前尘,问君当年何负少年心?
想分手却又苦(ai)于(yu)找(zi)不(ji)到(de)合(zha)适(nan)理(qing)由(mian)的情侣们,熟记圆周率,让你在情人节当天分手分得漂亮且得体。
世间万事皆遵循着一个因果,起因源自某乎上的一个问题:
自诩天生没有儿子命无论跟谁结婚都会生女儿的pusofalse版主对这样的题目真的毫无免疫力,于是从网上下载了y-cruncher.exe,借助此神器生成了一个包含Pi小数点后3亿位的文本文件,就此打算手工验证一下某乎上那个问题的真伪。思路中的原理很简单:
银行卡密码是由0~9这10个十进位数组成的6位数,所有可能的组合,自000000开始,到999999结束,一共包含1000000种可能的组合。把「包含小数点后3亿位数的文本文件」当成是一个大的字符串,然后在一个百万次的循环中依次判断000000~999999是否属于这个大字符串中的子串就可以了。例如字符串"world"、"o wor"、"Hel"都是字符串"Hello world"中的子串,而"Helloworld"不是。
用5秒钟整理出思路,然后用10分钟将这个思路的具体实施细节呈现出来。pusofalse有云:将问题思路具象化到一个不能再精分下去的程度,用汇编语言写实现代码真的再合适不过。核心代码不过百行尔尔:
主过程Main函数一开始,首先为后续过程定义了几个局部变量hFile, hFileMapping, lpBaseAddress等等,然后进行了必要的初始化(代码60~72行):
主要是局部变量的清零,以及设置控制台输出窗口的字体颜色,绿色加亮是个比较酷炫的颜色,因为比较接近XXXX中的颜色。
然后是将包含了Pi小数点后3亿位数的文本文件映射进内存里(74~88行):
84行MapViewOfFile函数返回后,eax中存放的就是Pi这个超大字符串的地址,add eax, 2将地址的值加2,是为了跳过字符串开头的"3.",只检索小数部分。
然后计算检索的次数,因为银行卡密码是由6位10进制数组成,所以检索次数总是等于1000000次,此时ecx=1000000(90~103行):
接下来开始在一个while循环中依次检索"000000~999999"这1000000个字符串是否属于Pi小数点后"14159265....."这个大字符串中的子串。检索操作是调用了_imp__strstr函数,由msvcrt.dll导出,其中的第一个参数esi指向大字符串"14159265......",edi包含需要检索的银行卡密码,即"000000"、"000001"、"000002" …… 一直到“999999"(代码110~140行):
需要强调的是,如果_imp__strstr检索成功,会返回子串在母串中的位置,然后代码会提示这个位置并且输出子串之前和之后的10位数。例如检索的银行卡密码是"141592",小数点后第一位便是这个密码,这个子串已经是第一位,之前的10位是不存在的,因而会引发内存越界访问,为了避免这种错误,代码用cmp eax, esi/cmovs eax, esi两条指令来判断会不会存在越界访问,如果越界,便修正到最开始第一位"141592"的位置。如果_imp__strstr没有检索成功,即母串中不存在指定的子串,代码会提示not found。
在一次检索操作完成以后,代码会调用IncreaseDecimalString函数来递增密码,IncreaseDecimalString内部的实现如下:
汇编语言的执行速度本身已经很快,但为了更快一点,IncreaseDecimalString函数的代码外用option prologue:none、option epilogue:none两条伪指令来取消了函数堆栈的建帧操作,即省去了函数入口处的push ebp、mov ebp, esp两条指令,以及函数出口处的mov esp, ebp、pop ebp两条指令,省去四条指令,对于短时量的任务而言,速度优势不会太明显,然而当量级上去以后,优化每条指令就显得很有必要了。比如能用lea ecx, [eax][1]一条指令时,坚决不用mov ecx, eax、inc ecx两条指令。
实测发现,Pi小数点后2千万位,真的包含了所有的6位数银行卡密码。实测还发现,3亿位可以检索出85%左右的8位数密码。6位数密码"141592"出现在第一位上,"415926"出现在第二位上,"159265"出现在第三位上,等等……
The string000540occurs at position254229of Pi, 0980729960000540296913908.
The string000541occurs at position2829351of Pi, 4638198430000541002994340.
The string000542occurs at position209292of Pi, 9997972507000542569584482.
The string000543occurs at position741585of Pi, 0791170549000543621962170.
…… ……
The string014078occurs at position458103of Pi, 8862995032014078355006817.
The string014079occurs at position1632229of Pi, 9030543959014079561017921.
The string014080occurs at position1102704of Pi, 6429182359014080463694906.
…… ……
The string036959occurs at position971314of Pi, 9336065885036959606666076.
The string036960occurs at position1682920of Pi, 6051868867036960003256553.
因缘混沌,情人节的当天,当自诩天生没有儿子命无论和谁结婚都会生女儿的pusofalse版主把5201314在Pi中的位置2823254发给自己心怡的女生时,出现了这样的情况 ——
情人节的当天,接受自己注孤生的命运是个难且令人绝望伤心的事。因着对这个凄凉故事里的女主角深深的爱,且容许「自诩天生没有儿子命无论和谁结婚都会生女儿的pusofalse版主」赋词一首,其声……即《虞美人》:
因缘混沌发机缄,此夜重相见。凭今回首忆前尘,问君当年何负少年心。
愿君失意身潦倒,我会XXX。若得一世一双人,祝你相隔相望不相亲。
…… …… ……
…… …… ……
…… ……
且慢……
—END—
转发随意,转载请注明作者及出处。
领取专属 10元无门槛券
私享最新 技术干货