Express的Session不同步问题

近期使用express做项目,发现遇到了一个神奇的bug,从现象上来看是用户的session并不同步。具体来说有如下现象——

  • 页面A在session中加入了某个字段。并输出session,发现字段已经成功加入。
  • 页面A通过setTimeout设置5秒后再次输出session,发现字段仍然存在。
  • 页面B在页面A加载后的5秒内访问同用户的session,发现字段不见了。

经过研究,解决方式为在session创建时,指明resave选项为false——

resave字段为false的时候,倘若一个请求未修改session本身,则session不会重新写回内存。通常倘若两个同时发生的请求,只有一个修改了session。当resave为true时,未修改session的请求的session写回操作很容易覆盖另一个请求对于session的修改。

原创文章地址:【Express的Session不同步问题】转载时请注明出处mhy12345.xyz

在Vue.js中使用Quill富文本编辑器自定义块级元素

再一次开发中,我试图在vue.js搭建的网站中引入富文本编辑器,我是用了vue2-editor. 这个库将开源富文本编辑器Quill包装进了vue.js的组件中。从易用性角度来说,非常令人满意。

不过,在我们的设计中,我们尝试使用富文本实现一个问卷系统,例如对于选择题,我们希望把一个包含了四个选项的“不可分割块级元素”作为一个整体,像插入图片一样插到富文本编辑器中。

实际上,插入块级元素本身是有先例可循的,比如视频插入模块,可以再富文本编辑器中插入一个iframe块。其代码可在github上面看到——

仿照上面的代码,我们实现我们自己的题目添加控件——

这是我们设计的独立功能模块,通过命令  Quill.register(SelectProblemEmbeder); 进行引入。在保存时,我们需要同时保存富文本的HTML信息,以及DELTAS信息,HTML信息可以用于渲染内容,可以通过vue2-editor暴露在外的content获取,而DELTAS信息可以用于保存富文本编辑的状态,供后续恢复。可以使用 let quill_deltas = JSON.stringify(this.quill.getContents());得到。其中, this.quill 是通过$refs获取到的模块内部信息。恢复时,我们读取上一次编辑的DELTAS信息,使用 this.quill.setContents(quill_deltas); 即可回复之前的状态。

原创文章地址:【在Vue.js中使用Quill富文本编辑器自定义块级元素】转载时请注明出处mhy12345.xyz

汇编解析C语言函数结构体struct参数的方式

本文介绍在x86汇编中没有加优化选项时的函数体struct传参和返回的方法。

结构体返回值

测试代码

我们先讲讲较为简单的函数返回结构体的方法——

上面的代码实现了一个非常简单的返回结构体的例子,对应的汇编代码如下——

分析

在上面的程序中,内层  return_struct 函数在被调用时,有两个有意义的寄存器,分别是 %rdi 记录了在function1中定义的那个结构体的位置, %rsi 记录传入的n。

在函数进入时,两个寄存器都被保存到了内存里面,接下来按部就班实现C++中的逻辑。重点部分在第30-35行,使用了三个位移操作赋值了五个值。最初理解代码,尝试寻找拷贝内存的语句时,将这三句话直接排除在外,实际上,由于结构体里面是5个整形,而使用64位寄存器,一次可以拷贝两个整数,因此三次拷贝恰好可以用最快的时间完成拷贝。

结论

因此,结构体返回的方式是将返回的结构体的起始位置指针放入 %rdi 寄存器,并在子程序中通过该寄存器定位修改结构体的内容。

结构体传参

测试代码

首先设计样例代码如下——

反汇编出的信息如下——

分析

有了上面的铺垫,我们可以迅速找到在30-35行有一段结构体的拷贝——将位于局部变量中的结构体拷贝到了在28行分配的一段连续空间中,而拷贝操作都是由 %rax 定位的。有意思的是,当我们认为 %rax 正是一个向子程序定位结构体的指针是,发现在子程序中 %rax 的寄存器中的值并没有被使用,反而是使用了 %rsp 进行定位。从另一个方面思考其实也并不奇怪,结构体参数本身是不能通过寄存器传参的,实际上,传参使用了最简单的方式——“通过压栈方式传参”,参数按照“从右到左”的顺序压栈,倘若遇到结构体,则压栈的内容变得多一些而已。

结论

结构体作为参数传入使用压栈方式传参,参数按照“从右到左”的顺序压栈,特别的,当参数为结构体,将结构体整体压栈。

原创文章地址:【汇编解析C语言函数结构体struct参数的方式】转载时请注明出处mhy12345.xyz

rcaudio:实时录音的音频特征处理库

简介

rcaudio是一个实时的录音,并处理录制的音频的库,使用rcaudio,你可以不用自己编写任何代码,而实现——

  • 获取原始的音频数据
  • 实时监测音量
  • 实时分析录音(歌曲)的节拍,并加以预测

安装

rcaudio是我的第一个放在了pypi.org的工程,因此可以使用pip安装 pip install rcaudio .其中该工程依赖 pyaudio ,倘若出现了无法找到头文件的错误,可直接重装覆盖系统默认的

rcaudio的源码地址为:https://github.com/mhy12345/rcaudio,欢迎push&issue

使用

CoreRecorder

CoreRecorder  是提取原始音频信息的类. 其本身会额外开一个进程,当成员函数 start() 被调用后,录音开始,音频数据会出存在一个队列 CoreRecorder.buffer 中。

如下是一个以字符形式输出录音的波形图的小程序

SimpleRecorder

在多数较为复杂的情况中,我们可以使用 SimpleRecorder ,从效率角度出发,推荐只实例化一次 SimpleRecorder因为绑定了录音部分,所以他会占用掉相当一部分的运行资源。

通过调用 SimpleRecorder 的成员函数 register() ,我们可以将多个 Analyzer 类绑定。

当成员函数 start() 被调用后,录音自动开始,同时起绑定的所有 Analyzer 实例也同时开始运行。

Analyzer

所有继承于 BaseAnalyzer 的类都是一个 Analyzer ,其功能是从原始的音频数据中提取出用户希望的信息。如下例子中,一个 VolumeAnalyzer 用于实时询问音量。

下面例子中,一个 BeatAnalyzer 用于分析节拍并进行预测。(这里的预测会有1~2s的延迟)。 BeatAnalyzer 本质上是通过在当前最后可用的若干 rec_time 时间的数据,提取起节拍信息,进而校准当前的节拍器。校准的具体算法可以参考我的另一篇文章python实现音频节拍的实时识别

多个Analyzer同样可行,比如如下程序在分析节拍的同时,通过音量判断乐曲是否结束。

如果你需要自定义音频特征的提取函数,不妨试试重写 FeatureAnalyzer 的 data_process 成员函数(其传入一个一维数组表示特定时间段的音频原始数据,返回这段时见的特征)。 默认的 data_process 函数计算了过零率。

注意事项

大部分的函数都存在1-2s的延时,个人认为如果不是把所有细节都在实时环境中重写一遍,是很难避免的。在 BeatAnalyzer 中,我使用了很多技巧来让用户看起来好像是事实的,而在 FeatureAnalyzer 如果特征的处理速度太慢,可能这个延时会被无限放大。如果这种情况真的发生,你可以考虑减少采样率sr,或者自己重写一个Analyzer来取舍一些不太重要的步骤。

最后

如果这个工程真的帮助你了的话,不妨给我一个Star哦,你的支持是我的动力!要是有pull request那我就幸福死了ovo

原创文章地址:【rcaudio:实时录音的音频特征处理库】转载时请注明出处mhy12345.xyz

Java多线程实现归并排序

Java中,一个class可以继承Thread类以实现多线程调用

 

原创文章地址:【Java多线程实现归并排序】转载时请注明出处mhy12345.xyz

Java使用正则表达式实现重叠匹配

问题:在一篇文章中一个匹配日期 yyyy-MM-dd 的程序。

显然一个日期对应Java中的正则表达式

然而,这样的正则表达式实现方式在处理如下输入数据时,无法提取出重叠的匹配

结果

这是由于第一个匹配消耗了其所在的所有字符的缘故,后续匹配将无法使用这些字符。这是我们需要使用 (?=((\\d{4})-(\\d{2})-(\\d{2}))). 正则表达式。

其中 (?=X) 这个语法匹配一个“位置”,这个位置向后可以完全匹配正则表达式X。 (\\d{4})-(\\d{2})-(\\d{2}) 是之前用于匹配日期的正则表达式, . 用于匹配单个字符。整个正则表达式语义为“匹配一个可以匹配日期字符串的位置及其后的一个字符”。

而原来的年月日信息可以通过 groups() 函数得到——

对应的整个程序为——

 

原创文章地址:【Java使用正则表达式实现重叠匹配】转载时请注明出处mhy12345.xyz

一句话解析汇编中的ret和call指令

这篇文章有点标题党了,因为实际上这ret和call还真没法一句话说完,向下读之前,读者需要知道——

  • 什么是,栈的pop和push指的是什么
  • 初步了解了栈在函数调用中的作用

汇编中,函数调用是通过栈维护的,栈顶指针称为%rsp。通常有两类汇编命令能修改%rsp:

  • 一类是直接使用add和sub汇编指令,这种情况多用于函数在栈中分配零时空间直接通过对%rsp加减指定数值得到。
  • 另一类是pop和push汇编指令,该指令隐含了一个%rsp加一或减一的操作,多用于试图保存一个寄存器的值供后续恢复。

另一方面,有个程序运行指令的计数器%rip,时时刻刻指向下一条指令的内存位置。一般情况下,%rip都是一个一个加下去的,特例是使用了jmp指令,则%rip直接被赋值为jmp的目标地址。

当汇编指令call被调用的时候,首先会隐式调用一个push命令,将储存有“之后本应该执行的语句的地址<CALLBACK>”的寄存器%rip的值放到栈顶,然后一个jmp指令通过修改%rip,将程序指令流跳转到目标位置。

当汇编指令ret被调用的时候,首先会有一个pop命令,将栈顶的值<CALLBACK>弹出,并放在%rip中作为下一个需要执行的代码。

关于汇编中的函数调用,我在另一篇文章解析汇编中实现函数调用的若干方式中有更详细的阐述。

原创文章地址:【一句话解析汇编中的ret和call指令】转载时请注明出处mhy12345.xyz

解析汇编中实现函数调用的若干方式

相同的c/c++语言的函数调用,在编译成汇编代码之后,会存在不同的解析方式,这些解析方式一方面依赖于编译器本身(比如gcc和g++编译出的代码就不尽相同),另一方面也会受到编译选项的影响(比如是否打开-O2 -fno-stack-protector这些编译开关)。网上很多其他的资料无视了实现函数调用的汇编代码的多样性,使得读者会在对照理解时出现一些困难。

在这篇文章中,我将通过几个小例子,介绍不同情况汇编实现C/C++语言函数调用的方式。具体的代码可以在 我的github 上面看到。


下图是一个简单的读入字符串的程序(注意,这个程序故意引入了不安全的gets函数)的C语言版本和C++版本——

 

这个程序中的getbuf函数就是我们今天讨论的主题

通过栈帧实现的函数调用

简单的例子

这是C语言代码版本通过编译命令 gcc c.c -o c -fno-stack-protector 生成的汇编代码——

理解%rsp和%rbp

其中两个重要的寄存器分别是 %rbp 和 %rsp , %rbp 称作帧指针,而 %rsp 称作栈指针。首先需要记住如下事实:

  • 函数的栈空间是从大地址空间向小地址空间生长。也就是说,在栈空间中的push操作会使 %rsp 越来越小。(对应右图中上面为高地址,下面为低地址)
  • 当前程序的局部变量储存在 %rsp 和 %rbp 两个指针所夹的那一段区域内(对应右图中紫色部分)

现在,在形式化定义 %rsp 和 %rbp :

%rsp 表示栈顶指针,永远指向当前使用栈空间中最下面的位置的地址。因此在我们的程序中,只用于定位栈顶的位置,并支持栈的插入弹出,而不用于地址索引。一般需要改变 %rsp 的地方有——

  • push指令:push指令将 %rsp 的值减一,并在新的 %rsp 位置放上需要push的值
  • pop指令:pop指令将 %rsp 的值加一,并将原来的 %rsp 位置上的值放在pop的寄存器中
  • 进入函数中,函数需要一定的空间,直接使用 sub $0x10,%rsp 分配整块的空间。
  • call指令和ret指令:其中内含了对于寄存器 %rip 的push和pop操作。关于call指令和ret指令更加详细的说明,在我的另一篇文章一句话解析汇编中的ret和call指令有更详细的解答。

%rbp 表示帧指针,一般在函数中用于定位,由于 %rsp 身兼多职,可能在一个函数块内修改,因此,给予 %rsp 的栈内信息定位是可行但不方便的。帧指针 %rbp 指向的位置是函数调用后,使用push操作保存上一个函数的帧指针后, %rsp 的位置,换句话说,这个位置就是没有进行任何局部变量分配是,栈顶 %rsp 的位置,之后的任何函数内的 %rsp 修改就都与 %rbp 无关了。从构造上来说, %rbp 还能用来实现函数的退出以及上一层函数的恢复。需要修改 %rbp 的地方有——

  • 调用函数时, %rbp 仍然指向的是上一层函数的帧,需要更新。为了之后可以恢复,需要将当前的 %rbp 的值使用push指令,放在栈顶,然后将 %rbp 赋值为 %rsp ,标记为“当前帧指针就是刚进入函数,没有分配任何内容的栈顶指针”。同时,新的 %rbp 指向的位置存有 %rbp 在函数退出时需要恢复的值。
  • 退出函数时,需要将%rbp更新回去。

汇编代码解读

回到之前的汇编代码,我们看看调用getbuf之后,究竟干了些啥:

整个getbuf的调用依次是——

  • 【位于main中】call指令,将程序结束下一条指令地址 %rip push至栈顶,并将call的函数入口地址赋值给 %rip ,实现指令流的跳转。
  • 保存旧的 %rbp 至栈顶,此时push使得 %rsp 加了1
  • 将栈顶指针 %rsp 赋值到 %rbp 中,此时新的 %rbp 可以理解成main函数内部变量和getbuf函数内部变量的分界线,而这个位置之后会作为getbuf函数内部变量定位的基准。
  • 函数内部变量需要使用接下来0x10大小的空间,在栈中预先分配
  • 我们将分配的那一块空间用来存buf数组,那么buf指针的值就应该是 %rbp-0x10 了
  • 将存有buf数组的寄存器 %rax 赋值给 %rdi
  • 将零时储存器 %rax 清零
  • 调用gets函数
  • 将储存器 %rax 赋值为1作为返回值
  • 使用leaveq指令恢复栈帧 %rbp
  • 使用ret指令将栈顶储存的“函数退出后下一条语句地址”弹出,并重定向为下一句执行位置。

C语言与C++语言

C++语言的getbuf函数汇编代码如下,可以发现,除了函数符号名有些区别外,没有其他的区别。

只使用栈指针实现函数调用

倘若在编译时,添加-O2优化,则C语言代码的getbuf部分变成了:

由于在之前的实现中, %rbp 的作用实际上是简化由于 %rsp 的变化导致定位偏移量不容易计算而设计出来的,如果我们成功追踪了 %rsp 在每个函数调用过程中的变化量,就很容易实现函数调用的恢复操作。

而在-O2优化下,编译器会以效率优先,如果我们完全知道了代码长什么样的,也知道每一次程序 %rsp 的变化量,实际上 %rbp 就不是那么有必要了。也就是说,这个程序是单纯使用 %rsp 实现函数调用的例子。

在上面程序中,getbuf调用步骤为:

  • 【main函数中】call指令调用getbuf,getbuf返回后下一条指令的地址被压栈,处理器指令运行的指针被置于getbuf其实位置
  • 移动 %rsp 分配足够空间
  • 清零 %eax
  • %rsp (即buf数组的地址),作为gets的参数,放在 %rdi 中
  • 调用gets函数
  • 将返回值放在 %eax 中
  • 将之前 %rsp 分配空间部分撤销
  • 退出

函数调用中的参数传递

尝试将原来的程序中,添加参数传入,得到下面的getbuf函数——

函数中传入了一大堆参数,这时因为,C++在传入参数少和传入参数多的时候,处理方式有所区别,因此,只有当 getbuf 函数传入足够多的参数时,才能成功涵盖两种方式。

将上述代码编译之后,得到汇编代码如下——

以及调用者main函数的反汇编代码。

 

可以发现,对于参数的传递,分为两种方式——

  • 当参数个数本身较少的时候,直接使用寄存器传参。对应了0x4005e6-0x400601的代码
  • 当参数个数比较多的话,无法用寄存器装下的参数,在调用前压到栈里面,由被调用者通过 %rbp 定位取之,在调用完成后,被调用者再将压栈的参数弹出。

在细节上,对于寄存器传参,依次使用了寄存器 %rdi , %rsi , %rdx , %rcx , %r8 , %r9 ;对于压栈传参,是按照参数位置,从右到左压栈。

包含金丝雀保护机制的函数调用

倘若编译参数中,没有 -fno-stack-protector,则C语言的getbuf编译出来如下——

多出来的一部分代码被称为金丝雀,是防止gets操作产生危险后果的,具体原理有时间再更~

环境与配置

gcc版本 (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609

g++版本 (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609

Ubuntu版本 16.04 64位

原创文章地址:【解析汇编中实现函数调用的若干方式】转载时请注明出处mhy12345.xyz

Latex平衡双栏引用的两栏高度

最近投论文,要求全文双栏,我使用了默认的 \bibliography

结果如图

主办方工作人员非常nice地告知需要平衡两栏长度,并且提供了详细的解决方案

  • 如果是使用普通方法手打引用的话,可以使用 \vfill\eject 命令分割需要换栏的位置。
  • 如果使用 \bibliography 生成引用的话,可以先  \usepackage{balance} ,然后在  \bibliographystyle 和 \bibliographystyle 之间加一句  \balance 。

改后引用页面如下——

原创文章地址:【Latex平衡双栏引用的两栏高度】转载时请注明出处mhy12345.xyz