Form Closure(形闭合):这是一种纯粹基于几何的定义。指的是接触点(contact points)形成了一个 “笼子”,将物体完全包住。在不移动接触点的情况下,物体从几何上无法从这个 “笼子” 中逃逸。可以认为这是一种最理想、最稳固的包裹式抓取接触状态。其不依赖于摩擦力。
Force Closure(力闭合):这个概念考虑了接触点的力和摩擦力。它指的是,虽然接触点可能没有形成几何上的 “笼子”,但通过在这些接触点上施加适当的力(利用摩擦力),可以抵抗施加在物体上的任意方向的力(force)和力矩(torque)。换句话说,只要夹爪(或手指)能提供足够大的力,理论上就能抵抗任何外来的扰动,或者能让物体产生任意方向的加速度和角加速度。其依赖于摩擦力。
而如果对于某一旋转表达方式,存在这种 Ground Truth 监督信号的跳变,神经网络为了拟合这种跳变点,就会导致其权重矩阵 $W$ 出现一些很大的参数,造成数值不稳定性的同时,为之消耗大量的注意力,大部分的训练过程都去拟合这种跳变而不是其他占比更多、更泛用的部分,这是非常不好的。并且这一过程是 Loss 无关的,是由于选择了不好的表达方式造成的本质性问题。
$$
\begin{aligned}
w &= \frac{\sqrt{\text{tr}(R)+1}}{2} \
x &= \frac{R_{32}-R_{23}}{4w} \
y &= \frac{R_{13}-R_{31}}{4w} \
z &= \frac{R_{21}-R_{12}}{4w}
\end{aligned}
$$
data:值的具体数据,根据 tag 的不同,有不同的结构体。如上文所说的 load 指令,其 tag 为 KOOPA_RVT_LOAD,其 data 为 koopa_raw_load_t 类型,而 koopa_raw_load_t 又会有 load 指令所需的 src 和 dest 两个指令字面字段。
类型 type
类型 type 对应 koopa_raw_type_t 类型,表示指向一个 koopa_raw_type_kind_t 类型的指针,用于描述值的静态类型,一个值可以是 int32、pointer、function 等。
$$
\begin{array}{l}
D \rightarrow T \ id \ ; \ D \ | \ \varepsilon \
T \rightarrow B \ C \ | \ \text{record} \ \text{“{”} \ D \ \text{“}”} \
B \rightarrow \text{int} \ | \ \text{float} \
C \rightarrow \varepsilon \ | \ [num] \ C \
\end{array}
$$
$$
\begin{aligned}
E &\rightarrow T E' \
E' &\rightarrow + T E' | \varepsilon \
T &\rightarrow F T' \
T' &\rightarrow * F T' | \varepsilon \
F &\rightarrow ( E ) | id
\end{aligned}
$$
$$
\begin{aligned}
E &\rightarrow T E' \
E' &\rightarrow + T E' | \varepsilon \
T &\rightarrow F T' \
T' &\rightarrow * F T' | \varepsilon \
F &\rightarrow ( E ) | id
\end{aligned}
$$
Traceback (most recent call last):
File "/home/ubuntu/.miniconda3/bin/ssserver", line 5, in <module>
from shadowsocks.server import main
File "/home/ubuntu/.miniconda3/lib/python3.12/site-packages/shadowsocks/server.py", line 27, in <module>
from shadowsocks import shell, daemon, eventloop, tcprelay, udprelay, \
File "/home/ubuntu/.miniconda3/lib/python3.12/site-packages/shadowsocks/udprelay.py", line 71, in <module>
from shadowsocks import encrypt, eventloop, lru_cache, common, shell
File "/home/ubuntu/.miniconda3/lib/python3.12/site-packages/shadowsocks/lru_cache.py", line 34, in <module>
class LRUCache(collections.MutableMapping):
^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: module 'collections' has no attribute 'MutableMapping'
AttributeError: /home/ubuntu/.miniconda3/lib/python3.12/lib-dynload/../../libcrypto.so.3: undefined symbol: EVP_CIPHER_CTX_cleanup. Did you mean: 'EVP_CIPHER_CTX_new'?
随着暑假的时间一天天在游戏、B 站、知乎中消磨殆尽,我逐渐萌生了出去旅行的想法。最开始的备选选项很多,杭州、广州、重庆、深圳……最终使我下定决心的契机出现在了 QQ 群,当我看到友人 L 因为要去广州办理美签的原因成功和另一位友人 S 面基,再又琢磨了一下似乎打出生以来我就没有去过广州这个城市,于是我选定了广州作为目的地。
走出了博物馆,离日落时分还有一个小时左右,便又回到了近在咫尺的广州市图书馆。好不容易才寻得一个座位坐下。尽管一路上发现很多人其实都没有在看书而是在做着各种和看书毫无关系的事情,但我最终还是没好意思在这个地方打开 B 站。于是懒得再去特地找书看的我便打开了还没看完的李沐老师的《Dive into Deep Learning》继续阅读,甚至在不懂的地方还去温习了一下一年前学的线性代数,虽然有些故作姿态的嫌疑,不过我最终也确实还是看了进去,乐。
询问了就读于中山大学的友人 X 得到了一家叫做彩点的本地美食推荐,到地之后才发现又忘了这里 14:00 下班的惯例,错过了饭点,只能在友人 X 的推荐下点了一些下午茶,虾饺皇与红米肠。端上来之后也口感果然超出了预期,皮薄馅多,虾仁鲜嫩多汁,口感丰富而不腻。不禁再次感叹广州美食之都的称号果然是名副其实。
大家就着服务器聊了半天八卦,作为一个技术组的成员确实是未曾听闻过这么多的故事,于是我一边上贡牛肉一边感慨果真是有人的地方就有江湖。我们一直聊到了店铺打烊才彼此告别。我想起昨天与友人 S 未能成行的江边闲聊,便又约着友人 S 到了江边畅聊。许是近日听闻的诸多别样人生给了我一些感怀,我破例饮了人生的第一杯酒——度数不高的葡萄酒,入口后带来的是一种怪异的感觉,口感绵长,味道介于涩和酸甜之间,换做平时的我大概会在喝了第一口后就弃之而去,可这次我还是喝完了,甚至还又饮了一些鲜啤。我们又开始了有一搭没一搭的闲聊……我打趣说自己此次广州行好像把自己从过往的人生轨迹中抽离了出来,而喝了酒之后又好似把本就抽离了的我又抽离了一次,我观察着自己身体的有趣变化,语言开始变得跳脱与偏向直觉,反应时间被延长,果然我还是很难喜欢上这种感觉,不过一次两次似乎也无妨就是了。
不知是酒精还是意识作用,这天仅仅睡了两个小时我便醒了,惯例的摸鱼了会后又才接着睡了回笼觉,再度睁眼已近下午两点。匆忙地办理了退房后开始思索今天的安排,想到晚上要去和友人 S 一起看 Live House,便把酒店订到了昨夜畅饮的太古仓附近。本想打车前往了酒店安置好行李再外出觅食,可看了一圈好像还是中山附近餐馆众多,于是拖着行李箱又随便找了一家港餐坐下大快朵颐。
逛完这里便又前往了和友人 X 约好的动漫星城,果然周末人流量远胜上次来的工作日,说一句人流如织并不过分。各种 coser 着实是又过了把眼瘾,本想着社恐的我拉上社牛友人 X 之后再去集邮,结果干饭的时候才得知他居然是带着女朋友来的,然后把女朋友放养了去买谷子来和我吃饭,他真的我哭死。
天光渐渐淡了下来,和友人 X 分别之后的我又在动漫星城漫无目的地逛了一会,最终在天闻角川的店里买了两个物件权当纪念。最终还是没能鼓起勇气去和 coser 合影,不过仅仅是四处逛逛便已是心满意足。
赶着 Live House 的开始时间到了现场,友人 S 迟到了我便独自一人先行进入了。这是我第一次听 Live House,当强劲的音浪袭来,我真切地感觉到五脏六腑都在与之共振,不得已套出了耳机打开降噪模式。我们听的是 There is a light 这个乐队,之前考试季听了许多许多遍他们演奏的《We choose to go to the moon》,可惜身临其境之时并未听到熟悉的旋律,不过乐队的演奏还是深深震撼了我的心灵,舞美的灯光交替闪烁,四周的人群随之舞动,而我也彻底沉浸在了音乐之中。
待到友人 S 赶到,一起听了一会便被他拉到场地后方,耳朵的压力也有所减小。
演出比预想的结束的要快,从 Live House 出来我们又闲聊着回到了昨晚畅饮的酒家附近,目睹了樊振东的夺冠。
我们的相处现在回想起来似乎是那么的无厘头:友人 S 穿着一身长衣长裤,趁我不注意把空调调到了 16℃,我直到冷得发颤才注意到;没带长衣服的我拿出了浴巾披着取暖,真的是颇为搞笑;学长的预约需要多台设备零点准时开抢,我便分了友人 S 一台手机来帮忙一起抢,结果发现电脑需要 2FA 验证还着急地催促着他搞快点;我掏出自己上课的笔记,和他吐槽还不如他自学的实用……
最快乐的时光出现在友人 M 突然发来消息说服务器的网站宕机了,于是我们便开始凌晨一两点的上工。友人 S 是技术达人,可最终最复杂的保护措施拦住的都是自己。需要远程回到家里网络环境上工的友人 S 被自己设下的保护措施拦在外面,用我的电脑尝试许久也没能成功抵达自己的主机,直到最终才发现是酒店公用 WiFi 的网段与家用网段重合了才导致原本正常的措施一一失败。
赛博回家的友人 S 很快就修好了问题,而时钟也逐渐转到了接近四点多,我们这才再次分别。躺在床上的我闭眼回忆着这一晚的乐子,心想着不知何时才会再有这么快乐的时光……
Day 5
最后一天的行程原定是去昨天没能成行的美术馆看看,结果一起床就收到了友人 S 的消息告知美术馆周一闭馆,顿时开始懊恼为什么昨天没有和保安强硬一点闯进去速通一下,不过正如懂王说的,“也许这就是人生”,总是有着各种偏离预期,总是有着各种不如意,但本就是为了旅游而来的我也很快释怀了,本就已经相当尽兴,留下些许遗憾又算得上什么呢?
$$
\begin{aligned}
&\text{Input }\pi,\text{the policy to be evaluated}\
&\text{Initialize an array }V(s)=0,\text{for all }s\in\mathcal{S}\
&\text{Repeat}\
&\quad\Delta\leftarrow0\
&\quad\text{For each }s\in\mathcal{S}{:}\
&\quad\quad v\leftarrow V(s)\
&\quad\quad V(s)\leftarrow\sum_{a}\pi(a|s)\sum_{s^{\prime},r}p(s^{\prime},r|s,a)\big[r+\gamma V(s^{\prime})\big]\
&\quad\quad\Delta\leftarrow\max(\Delta,|v-V(s)|)\&\text{until }\Delta<\theta\text{ (a small positive number)}\&\text{Output }V\approx v_{\pi}\end{aligned}
$$
$$
\begin{array}{l}
\text{// \textbf{定义} 树搜索函数,接受一个问题作为输入,返回解决方案或失败} \
\textbf{function} \ \text{TREE-SEARCH}(\text{problem}) \ \text{returns a solution, or failure} \
\quad \text{// 初始化边界(待探索队列),使用问题的初始状态} \
\quad \text{initialize the frontier using the initial state of } \text{problem} \
\quad \text{// 循环开始} \
\quad \text{loop do} \
\quad \quad \text{// 如果边界为空,则返回失败} \
\qquad \text{if the frontier is empty then return failure} \
\quad \quad \text{// 选择一个叶子节点并从边界中移除} \
\qquad \text{choose a leaf node and remove it from the frontier} \
\quad \quad \text{// 如果节点包含目标状态,则返回对应的解决方案} \
\qquad \text{if the node contains a goal state then return the corresponding solution} \
\quad \quad \text{// 扩展所选节点,将结果节点添加到边界} \
\qquad \text{expand the chosen node, adding the resulting nodes to the frontier} \
\end{array}
$$
初始化:使用问题的初始状态来初始化边界(frontier)。
循环:通过不断循环来从边界中选择并移除叶节点,如果该节点包含目标状态,则返回对应的解决方案。
扩展节点:如果节点不包含目标状态,则扩展该节点,将结果节点添加到边界中。
图搜索 Graph-Search
$$
\begin{array}{l}
\text{// \textbf{定义} 图搜索函数,接受一个问题作为输入,返回解决方案或失败} \
\textbf{function} \ \text{GRAPH-SEARCH}(\text{problem}) \ \text{returns a solution, or failure} \
\quad \text{// 初始化边界(待探索队列),使用问题的初始状态} \
\quad \text{initialize the frontier using the initial state of } \text{problem} \
\quad \text{// 初始化已探索集合为空} \
\quad \textbf{initialize the explored set to be empty} \
\quad \text{// 循环开始} \
\quad \text{loop do} \
\quad \quad \text{// 如果边界为空,则返回失败} \
\qquad \text{if the frontier is empty then return failure} \
\quad \quad \text{// 选择一个叶子节点并从边界中移除} \
\qquad \text{choose a leaf node and remove it from the frontier} \
\quad \quad \text{// 如果节点包含目标状态,则返回对应的解决方案} \
\qquad \text{if the node contains a goal state then return the corresponding solution} \
\quad \quad \text{// 将节点添加到已探索集合} \
\qquad \textbf{add the node to the explored set} \
\quad \quad \text{// 扩展所选节点,将结果节点添加到边界} \
\qquad \text{expand the chosen node, adding the resulting nodes to the frontier} \
\quad \quad \quad \text{// 只有当节点不在边界或已探索集合中时} \
\qquad \quad \textbf{only if not in the frontier or explored set} \
\end{array}
$$
Set-Alias -Name open -Value explorer.exe
Invoke-Expression (&starship init powershell)
function Touch-File {
param($fileName)
New-Item $fileName -ItemType file
}
Set-Alias touch Touch-File
# Import the Chocolatey Profile that contains the necessary code to enable
# tab-completions to function for `choco`.
# Be aware that if you are missing these lines from your profile, tab completion
# for `choco` will not function.
# See https://ch0.co/tab-completion for details.
$ChocolateyProfile = "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1"
if (Test-Path($ChocolateyProfile)) {
Import-Module "$ChocolateyProfile"
}
function ChangeToProjectDirectory {
cd D:\Project
}
Set-Alias -Name repo -Value ChangeToProjectDirectory
#!/usr/bin/osascript
# Required parameters:
# @raycast.schemaVersion 1
# @raycast.title Lobe Chat
# @raycast.mode silent
# Optional parameters:
# @raycast.icon ./icons/lobe-chat.png
# @raycast.packageName Open/Hide Lobe Chat
# Documentation:
# @raycast.author Arthals
# @raycast.authorURL https://raycast.com/Arthals
-- AppleScript to check if LobeChat is the frontmost application
tell application "System Events"
set frontmostProcess to first application process whose frontmost is true
set frontmostApp to displayed name of frontmostProcess
end tell
-- Determining if LobeChat is the frontmost application
if frontmostApp is "LobeChat" then
set isLobeChatFrontmost to true
else
set isLobeChatFrontmost to false
end if
-- Output the status of LobeChat (whether it is frontmost or not)
-- AppleScript to hide LobeChat
if isLobeChatFrontmost then
tell application "System Events" to set visible of process "LobeChat" to false
else
-- AppleScript to bring LobeChat to the front
tell application "LobeChat" to activate
end if
on "LobeChat" to activate
end if
# 带小数点,浮点数转表示
./fshow 12.0
# Floating point value 12
# Bit Representation 0x41400000, sign = 0, exponent = 0x82, fraction = 0x400000
# Normalized. +1.5000000000 X 2^(3)
# 不带小数点,表示转浮点数
./fshow 12
# Floating point value 1.681558157e-44
# Bit Representation 0x0000000c, sign = 0, exponent = 0x00, fraction = 0x00000c
# Denormalized. +0.0000014305 X 2^(-126)
# 不带小数点,以 0x 开头,十六进制表示转浮点数
./fshow 0x41400000
# Floating point value 12
# Bit Representation 0x41400000, sign = 0, exponent = 0x82, fraction = 0x400000
# Normalized. +1.5000000000 X 2^(3)
bitXnor
要求:仅使用 ~ 和 | 来实现 ~(x ^ y)
允许的操作符:~、|
最大操作次数:7
评分:1
利用离散数学中学过的德摩根律,我们可以对此式进行变换:
$$
\begin{align*}
\because x \text { AND } y &= \sim(\sim x \text{ OR } \sim y) \
\therefore x \text{ XNOR } y &= \sim(x \text{ XOR } y) \
&= (\sim x \text{ AND } \sim y) \text{ OR } (x \text{ AND } y ) \
&= \sim(x \text{ OR } y) \text{ OR } \sim(\sim x \text{ OR } \sim y) \
\end{align*}
$$
$$
\text{bitXnor}(x, y) = \sim(x \text{ OR } y) \text{ OR } \sim(\sim x \text{ OR } \sim y)
$$
注:XNOR 是 XOR 运算的否定。
所以我们得到:
int bitXnor(int x, int y) {
return ~(x | y) | ~(~x | ~y);
}
bitConditional
要求:对每个比特(位)分别执行 x ? y : z
允许的操作符:~、&、^、|
最大操作次数:4
评分:1
我们利用 x 的每一位,来决定选择 y 还是 z 的一位(也即利用 & 的短路特性):
int bitConditional(int x, int y, int z) {
// 对每一位,如果 x_i 为 1,那么选择 y_i,否则选择 z_i
return (x & y) | (~x & z);
}
byteSwap
要求:交换第 n 字节和第 m 字节
允许的操作符:!、~、&、^、|、+、<<、>>
最大操作次数:16
评分:2
想要完成这个谜题,需要我们理解一个叫做 mask(掩码)的概念。
考虑如下真值表:
| x | y | x & y | x | y |
| --- | --- | ----- | ------ |
| 0 | 0 | 0 | 0 |
| 0 | 1 | 0 | 1 |
| 1 | 0 | 0 | 1 |
| 1 | 1 | 1 | 1 |
我们可以发现,当 x 为 0 时,无论 y 为什么,x & y 都为 0;当 x 为 1 时,无论 y 为什么,x & y 都为 y。
考虑到 1 个字节有 8 位,我们可以构造 mask 如下,其实现了对于 x 只保留第 n 个字节,而将其他字节清零的目的:
// n << 3 表示 n * 8
int n_byte_mask = 0xff << (n << 3);
int n_byte = x & n_byte_mask;
我们继续利用这个特性,来实现 byteSwap:
int byteSwap(int x, int n, int m) {
// 首先保存原始x,然后在对应的byte上按位与消去,再按位或上交换后的byte
int origin = x, clip_mask, swap_mask;
n <<= 3, m <<= 3;
// 0xff << n 表示将第 n 个字节保留,其他字节清零
// 取反后,表示将第 n 个字节清零,其他字节保留
clip_mask = ~((0xff << n) | (0xff << m));
x &= clip_mask;
// 先通过右移 n*8 位,将第 n 个字节移动到第 0 个字节,与 0xff 进行与运算,得到第 n 个字节,再左移 m*8 位,即实现将第 n 个字节移动到第 m 个字节
swap_mask = (0xff & (origin >> n)) << m | (0xff & (origin >> m)) << n;
// 完形填空
x |= swap_mask;
return x;
}
最容易想到的方法就是,通过将 x 分别左移、右移 1 位,获得两个 mask,再以这两个 mask 取或构造出一个新的、标志了连续 1 的 consecutive1_mask,对之取反,即得需要保留的位。最后将 x 与 ~consecutive1_mask 进行 & 运算,即可实现清除连续 1 的目的。
int cleanConsecutive1(int x) {
// 左右移动一位成为mask,左侧mask要额外考虑算数右移对最高位的影响
int right_mask = x << 1;
int left_mask = x >> 1 & ~(1 << 31);
x &= ~(right_mask | left_mask);
return x;
}
另外一种思路十分精巧,通过首先执行 int cur_and_right_mask = x & (x << 1),获得了一个当前位和右侧位都为 1 的掩码,随后执行 cur_and_right_mask | (cur_and_right_mask >> 1),实现了对于连续 1 的掩码。
int leftBitCount(int x) {
int ans = 0;
if (x 最高 16 位均为 1){
ans += 16;
x = x << 16;
}
if (x 最高 8 位均为 1){
ans += 8;
x = x << 8;
}
if (x 最高 4 位均为 1){
ans += 4;
x = x << 4;
}
if (x 最高 2 位均为 1){
ans += 2;
x = x << 2;
}
if (x 最高 1 位均为 1){
ans += 1;
x = x << 1;
}
// 对于 32 位全为 1 的情况特判
if (x == -1){
ans += 1;
}
}
接下来,我们需要思考如何使用位运算,实现判断 x 最高 16 位是否均为 1。
这等价于判断 ~x 最高 16 位是否均为 0。
基于此,我们设计实现思路如下:
int tmin = 1 << 31;
int is_high_16_all_1 = !(~(x & (tmin >> 15)) >> 16)
或许你会问为什么不直接使用 b 0x215f 和 b 0x219c 来设置断点?这是因为我们的炸弹每次运行的地址都不一样,所以我们需要使用相对地址来设置断点。而这点你会在后续学习第七章链接的时候有所了解,或者是在做下一个 Attack Lab 的时候就会知道了,这就是地址随机化(Address Space Layout Randomization,ASLR)。
int phase_4(int a, int b) {
if (a != 5){
explode_bomb();
}
int ebp = 0;
for (int ebx = 0; ebx < a; ebx++) {
ebp += func4(ebx);
}
if (ebp != b) {
explode_bomb();
}
}
首先我们要明确网络编程的本质,就是在两个不同的主机之间进行数据交换。因为我们的主机并不是总是和远程主机之间连了一根网线(回忆一下,这就是局域网 LAN),而是通过路由器、交换机等设备连接到了互联网上,走了一个 LAN - WAN - LAN 的过程,所以我们需要在浩如烟海的互联网中找到我们要通信的主机,这就是 IP 地址的作用。而找到远程主机之后,我们还需要找到远程主机上实际为我们提供服务的进程,这就是端口号的作用。
从一个域名获取 IP 地址的过程,就是 DNS 解析的过程,这个过程是由操作系统完成的,我们只需要调用 getaddrinfo 函数即可。在此不再展开更具体的细节,请阅读书上相关内容。值得一提的就是一定要注意区分 getaddrinfo 和 getnameinfo 两个函数,前者是从域名获取 IP 地址,后者是从 IP 地址获取域名,以及 getaddrinfo 获得的解析信息是一个链表,当你使用完之后,亦需要使用 freeaddrinfo 函数释放内存。
#
# Makefile for Proxy Lab
#
# You may modify this file any way you like (except for the handin
# rule). Autolab will execute the command "make" on your specific
# Makefile to build your proxy from sources.
#
CC = gcc
CFLAGS = -g -Wall
LDFLAGS = -lpthread
all: proxy
csapp.o: csapp.c csapp.h
$(CC) $(CFLAGS) -c csapp.c
cache.o: cache.c cache.h
$(CC) $(CFLAGS) -c cache.c
proxy.o: proxy.c csapp.h
$(CC) $(CFLAGS) -c proxy.c
proxy: proxy.o csapp.o cache.o
$(CC) $(CFLAGS) proxy.o csapp.o cache.o -o proxy $(LDFLAGS)
# Creates a tarball in ../proxylab-handin.tar that you should then
# hand in to Autolab. DO NOT MODIFY THIS!
handin:
(make clean; cd ..; tar czvf proxylab-handin.tar.gz proxylab-handout)
clean:
rm -f *~ *.o proxy core *.tar *.zip *.gzip *.bzip *.gz
然后,我们就可以使用 make 命令编译我们的代码了。
# 编译并运行代理服务器
make clean && make && ./proxy 7777 &
# 编译并运行内容服务器
cd ./tiny && make clean && make && ./tiny 7778 & cd ..
# 测试
curl -v --proxy http://localhost:7777 http://localhost:7778/
* Trying 127.0.0.1:7777...
* Connected to (nil) (127.0.0.1) port 7777 (#0)
> GET http://localhost:7778/ HTTP/1.1
> Host: localhost:7778
> User-Agent: curl/7.81.0
> Accept: */*
> Proxy-Connection: Keep-Alive
>
Accepted connection from (localhost, 55150)
GET / HTTP/1.0
Host: localhost:7778
Accept: */*
Connection: close
Proxy-Connection: close
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:10.0.3) Gecko/20120305 Firefox/10.0.3
Response headers:
HTTP/1.0 200 OK
Server: Tiny Web Server
Connection: close
Content-length: 120
Vary: *
Cache-Control: no-cache, no-store, must-revalidate
Content-type: text/html
* Mark bundle as not supporting multiuse
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Server: Tiny Web Server
< Connection: close
< Content-length: 120
< Vary: *
< Cache-Control: no-cache, no-store, must-revalidate
< Content-type: text/html
<
<html>
<head><title>test</title></head>
<body>
<img align="middle" src="godzilla.gif">
Dave O'Hallaron
</body>
</html>
* Closing connection 0
本地评分
make clean && make && ./driver.sh
得到输出:
*** Basic ***
Starting tiny on 11564
Starting proxy on 30943
1: home.html
Fetching ./tiny/home.html into ./.proxy using the proxy
Fetching ./tiny/home.html into ./.noproxy directly from Tiny
Comparing the two files
Success: Files are identical.
2: csapp.c
Fetching ./tiny/csapp.c into ./.proxy using the proxy
Fetching ./tiny/csapp.c into ./.noproxy directly from Tiny
Comparing the two files
Success: Files are identical.
3: tiny.c
Fetching ./tiny/tiny.c into ./.proxy using the proxy
Fetching ./tiny/tiny.c into ./.noproxy directly from Tiny
Comparing the two files
Success: Files are identical.
4: godzilla.jpg
Fetching ./tiny/godzilla.jpg into ./.proxy using the proxy
Fetching ./tiny/godzilla.jpg into ./.noproxy directly from Tiny
Comparing the two files
Success: Files are identical.
5: tiny
Fetching ./tiny/tiny into ./.proxy using the proxy
Fetching ./tiny/tiny into ./.noproxy directly from Tiny
Comparing the two files
Success: Files are identical.
Killing tiny and proxy
Basic: 40 / 40
*** Concurrency ***
Starting tiny on port 33268
Starting proxy on port 33465
Starting the blocking NOP server on port 13382
Trying to fetch a file from the blocking nop-server
Fetching ./tiny/home.html into ./.noproxy directly from Tiny
Fetching ./tiny/home.html into ./.proxy using the proxy
Checking whether the proxy fetch succeeded
Success: Was able to fetch tiny/home.html from the proxy.
Killing tiny, proxy, and nop-server
Concurrency: 15 / 15
*** Cache ***
Starting tiny on port 19474
Starting proxy on port 21446
Fetching ./tiny/tiny.c into ./.proxy using the proxy
Fetching ./tiny/home.html into ./.proxy using the proxy
Fetching ./tiny/csapp.c into ./.proxy using the proxy
Killing tiny
Fetching a cached copy of ./tiny/home.html into ./.noproxy
Success: Was able to fetch tiny/home.html from the cache.
Killing proxy
Cache: 15 / 15
*** Real Pages ***
Starting proxy on port 13757
Starting tiny on 4999
Setup done, running webdriver
= launching chrome 1704912479.618855
no display available. going headless.
= loading page 1704912481.7444584
url: http://127.0.0.1.nip.io:4999/browser-testbench/index.html
title: Browser Testbench
= running test 1704912482.7150698
Open connect to 127.0.0.1.nip.io:65432 error
Parse request line error: Success
Open connect to maruyama.pico:80 error
Send response to client error
Send response to client error
= retrieving score 1704912495.365371
passed tests reported by browser: 9
so you get 18 score
Log:
= finished 1704912495.4213529
Killing tiny and proxy
Real Pages: 18 / 18
totalScore = 88 / 88
{ "scores": {"Basic":40, "Concurrency":15, "Caching":15, "Real Pages":18},"scoreboard": [88, 40, 15, 15, 18]}
...
a 521 448
a 522 64
a 523 448
a 524 64
a 525 448
a 526 64
a 527 448
a 528 64
a 529 448
a 530 64
a 531 448
a 532 64
a 533 448
a 534 64
a 535 448
a 536 64
a 537 448
...
f 521
f 523
f 525
f 527
f 529
f 531
f 533
f 535
f 537
f 539
f 541
f 543
f 545
f 547
f 549
...
a 2000 512
a 2001 512
a 2002 512
a 2003 512
a 2004 512
a 2005 512
a 2006 512
a 2007 512
a 2008 512
a 2009 512
a 2010 512
a 2011 512
a 2012 512
a 2013 512
/*
* mm_checkfreelist: 检查空闲链表的正确性
*/
void mm_checkfreelist(int lineno) {
// 初始化对比变量
size_t free_block_by_list = 0;
size_t free_block_by_heap = 0;
// 检查所有链表链接的正确性
for (int i = 0;i < FREE_LIST_NUM;i++) {
char* bp = free_lists[i];
// 设置全局变量 low_range high_range 对应当前桶的大小范围
get_range(i);
while ((void*)bp > mem_heap_lo() && (void*)bp < mem_heap_hi()) {
// 检查双向链表是否匹配
if (PREV_NODE(bp) != NULL) {
if (NEXT_NODE(PREV_NODE(bp)) != bp) {
dbg_printf("[%d]Free List Error: prev and next pointer not match at %p\n", lineno, bp);
}
}
// 检查当前节点大小是否符合桶大小
size_t cur_size = GET_SIZE(HDRP(bp));
if (cur_size < low_range || cur_size > high_range) {
dbg_printf("[%d]Free List Error: block size not match bucket at %p\n", lineno, bp);
}
// 检查当前节点是否为空闲块
if (GET_ALLOC(HDRP(bp))) {
dbg_printf("[%d]Free List Error: block is not free at %p\n", lineno, bp);
}
bp = NEXT_NODE(bp);
++free_block_by_list;
}
if ((void*)bp != mem_heap_lo()) {
dbg_printf("[%d]Free List Error: pointer out of range at %p\n", lineno, bp);
}
}
char* bp = mem_heap_lo() + DSIZE * FREE_LIST_NUM;
while ((void*)bp < mem_heap_hi()) {
if (!GET_ALLOC(HDRP(bp))) {
++free_block_by_heap;
}
bp = NEXT_BLKP(bp);
}
// 检查对比变量是否匹配,从而确定是否所有的空闲块都在空闲链表中
if (free_block_by_heap != free_block_by_list) {
dbg_printf("[%d]Free List Error: not all free block in free lists %p\n", lineno, bp);
}
}
Modify 修改操作 = Load 加载操作 + Store 存储操作,所以在 M 操作时,需要访问两次缓存。
你编写的程序不用支持 -v 参数,所以你可以使用如下的跳转表:
switch (operation) {
case 'I':
continue;
case 'M': // Modify = Load + Store
useCache(address);
case 'L': // Load
case 'S': // Store
useCache(address);
}
这种写法可以让 M 操作直接多执行一次 useCache 函数,而不用再写一遍。但是,如果你想追求效率(尽管这个 Part 并不要求)或者想要支持 -v 参数,那么你可以直接多给 useCache 传递一个 is_modify 参数,来判断是否为 M 操作。若是,则可以直接令第二次写为 HIT,而不用再次访问缓存。具体实现可以参考我的代码。
值得一提的是,在所有的测试样例中,只有 mem.trace 中存在 M 操作,而 handout 中给出的测试命令行均没有测试它,也就没有测试 M 操作的正确性。你必须使用 test-csim 来测试 M 操作的正确性。
地址是 64 位,而不是 32 位
在 csim.c 中,地址是 64 位的,而不是 32 位的。所以你需要使用 %lx 来读取地址。同时你不能使用 int 类型来存储地址,而应该使用 unsigned long 或者 __uint64_t 类型或者 size_t 类型(执行的机器是 64 位的)。
所以,当 i = j 时,两个块的 Set 会发生冲突,导致大量的不命中。而当 i != j 时,两个块的 Set 不会发生冲突。
对于读 A 操作来说,我们往往具有很好的局部性,因为是行优先读,所以每 8 个数,除了第一次加载,后面的 7 次都会命中。
对于写 B 操作来说,我们完全没有利用局部性。考虑我们写 B 的第 j 行,我们第一次写(B[j][0])和第二次写(B[j][1])之间至少包括了对 B 的一整列(列优先,31 行,B[j+1][0] ~ B[j-1][1])的全部写,而我们的缓存大小只有 256 个数的大小(即 8 行),当前行早就被刷掉了,所以我们写 B 完全没机会出现命中。而且在对角线处时,我们还会因为要写 B 而导致对于已经读入的 A 的行发生冲突不命中(即要发生一次驱逐)。
所以整体计算下来,应当发生 1024(写 B 全部不命中)+ 4(写 A 每行每 8 个数不命中一次)* 32(写 A 的行)+ 32(对角线处写 B 造成读 A 驱逐,需要重新读一次),即 1184 次不命中。
L 10e0c0,4 miss
S 14e4a0,4 miss eviction
L 10e0c4,4 hit
S 14e520,4 miss eviction
L 10e0c8,4 hit
S 14e5a0,4 miss eviction
L 10e0cc,4 hit
S 14e620,4 miss eviction
L 10e0d0,4 hit
S 14e6a0,4 miss eviction
L 10e0d4,4 hit
S 14e720,4 miss eviction
L 10e0d8,4 hit
S 14e7a0,4 miss eviction
L 10e0dc,4 hit
S 14e820,4 miss eviction
L 10e0e0,4 miss eviction
S 14e8a0,4 miss eviction
于是,我们很自然的想到,通过限制写 B 的范围,来利用 B 的局部性。而这也就是书上讲过的分块技巧:将 32x32 的矩阵分成 8x8 的小块,这样就可以充分利用局部性,读 A 一次连续读入 8 个元素,然后转置,再连续写入 B。即避免了对于写 B 时,因为列优先顺序写造成的缓存驱逐。
同时我们使用多个局部变量来存储从 A 读出来的数据,这样可以最大化地利用局部性。
void transpose_submit(int M, int N, int A[N][M], int B[M][N]) {
REQUIRES(M > 0);
REQUIRES(N > 0);
// s = 5, E = 1, b = 5
// 总变量:4 个循环变量 + 8 个临时变量 = 12 个变量
int a, b, c, d, e, f, g, h;
if (M == 32) {
// 先把 A 复制 B,再转置 B,避免因为 A 的下一次读驱逐 B 的同一行,导致 B 的下一次写 MISS
// 8*8 分块
// 总 MISS:16(块数)*[8(读)+8(写)] = 256
// 显示 MISS = 260,但是通过添加 trans() 的代码并清空缓存,然后对比测试差异,可知实际只有 256 个 MISS
// 故猜测那 4 个多的 MISS 可能是别的函数调用所致,也可通过观察 trace.f0 发现确实开头多了 1 个 S 和 3 个 L
for (int i = 0; i < N; i += 8) { // 当前行
for (int j = 0; j < M; j += 8) { // 当前列
// 首先将 A[i][j]~A[i+7][j+7] 复制到 B[j][i]~B[j+7][i+7]
for (int k = 0;k < 8;++k) {
a = A[i + k][j];
b = A[i + k][j + 1];
c = A[i + k][j + 2];
d = A[i + k][j + 3];
e = A[i + k][j + 4];
f = A[i + k][j + 5];
g = A[i + k][j + 6];
h = A[i + k][j + 7];
B[j][i + k] = a;
B[j + 1][i + k] = b;
B[j + 2][i + k] = c;
B[j + 3][i + k] = d;
B[j + 4][i + k] = e;
B[j + 5][i + k] = f;
B[j + 6][i + k] = g;
B[j + 7][i + k] = h;
}
}
}
}
ENSURES(is_transpose(M, N, A, B));
}
# Stages for iaddq V, rB: add constant V to rB
#
# * Fetch
# * icode:ifun ← M1[PC]
# * rA:rB ← M1[PC+1]
# * valC ← M8[PC+2]
# * valP ← PC+10
# * Decode
# * valB ← R[rB]
# * Execute
# * valE ← valC+valB
# * Set CC
# * Memory
# * Write Back
# * R[rB] ← valE
# * PC Update
# * PC ← valP
# ---------------------------
# Stages for jm rB, V: jump to M[rB+V]
#
# * Fetch
# * icode:ifun ← M1[PC]
# * rA:rB ← M1[PC+1]
# * valC ← M8[PC+2]
# * valP ← PC+10
# * Decode
# * valB ← R[rB]
# * Execute
# * valE ← valC+valB
# * Memory
# * valM ← M8[valE]
# * Write Back
# * PC Update
# * PC ← valM
#/* $begin seq-all-hcl */
####################################################################
# HCL Description of Control for Single Cycle Y86-64 Processor SEQ #
# Copyright (C) Randal E. Bryant, David R. O'Hallaron, 2010 #
####################################################################
## Your task is to implement the iaddq instruction
## The file contains a declaration of the icodes
## for iaddq (IIADDQ)
## Your job is to add the rest of the logic to make it work
####################################################################
# C Include's. Don't alter these #
####################################################################
quote '#include <stdio.h>'
quote '#include "isa.h"'
quote '#include "sim.h"'
quote 'int sim_main(int argc, char *argv[]);'
quote 'word_t gen_pc(){return 0;}'
quote 'int main(int argc, char *argv[])'
quote ' {plusmode=0;return sim_main(argc,argv);}'
####################################################################
# Declarations. Do not change/remove/delete any of these #
####################################################################
##### Symbolic representation of Y86-64 Instruction Codes #############
wordsig INOP 'I_NOP'
wordsig IHALT 'I_HALT'
wordsig IRRMOVQ 'I_RRMOVQ'
wordsig IIRMOVQ 'I_IRMOVQ'
wordsig IRMMOVQ 'I_RMMOVQ'
wordsig IMRMOVQ 'I_MRMOVQ'
wordsig IOPQ 'I_ALU'
wordsig IJXX 'I_JMP'
wordsig ICALL 'I_CALL'
wordsig IRET 'I_RET'
wordsig IPUSHQ 'I_PUSHQ'
wordsig IPOPQ 'I_POPQ'
# Instruction code for iaddq instruction
wordsig IIADDQ 'I_IADDQ'
#Instruction code for jm instruction
wordsig IJM 'I_JM'
##### Symbolic represenations of Y86-64 function codes #####
wordsig FNONE 'F_NONE' # Default function code
##### Symbolic representation of Y86-64 Registers referenced explicitly #####
wordsig RRSP 'REG_RSP' # Stack Pointer
wordsig RNONE 'REG_NONE' # Special value indicating "no register"
##### ALU Functions referenced explicitly #####
wordsig ALUADD 'A_ADD' # ALU should add its arguments
##### Possible instruction status values #####
wordsig SAOK 'STAT_AOK' # Normal execution
wordsig SADR 'STAT_ADR' # Invalid memory address
wordsig SINS 'STAT_INS' # Invalid instruction
wordsig SHLT 'STAT_HLT' # Halt instruction encountered
##### Signals that can be referenced by control logic ####################
##### Fetch stage inputs #####
wordsig pc 'pc' # Program counter
##### Fetch stage computations #####
wordsig imem_icode 'imem_icode' # icode field from instruction memory
wordsig imem_ifun 'imem_ifun' # ifun field from instruction memory
wordsig icode 'icode' # Instruction control code
wordsig ifun 'ifun' # Instruction function
wordsig rA 'ra' # rA field from instruction
wordsig rB 'rb' # rB field from instruction
wordsig valC 'valc' # Constant from instruction
wordsig valP 'valp' # Address of following instruction
boolsig imem_error 'imem_error' # Error signal from instruction memory
boolsig instr_valid 'instr_valid' # Is fetched instruction valid?
##### Decode stage computations #####
wordsig valA 'vala' # Value from register A port
wordsig valB 'valb' # Value from register B port
##### Execute stage computations #####
wordsig valE 'vale' # Value computed by ALU
boolsig Cnd 'cond' # Branch test
##### Memory stage computations #####
wordsig valM 'valm' # Value read from memory
boolsig dmem_error 'dmem_error' # Error signal from data memory
####################################################################
# Control Signal Definitions. #
####################################################################
################ Fetch Stage ###################################
# Determine instruction code
word icode = [
imem_error: INOP;
1: imem_icode; # Default: get from instruction memory
];
# Determine instruction function
word ifun = [
imem_error: FNONE;
1: imem_ifun; # Default: get from instruction memory
];
bool instr_valid = icode in
{ INOP, IHALT, IRRMOVQ, IIRMOVQ, IRMMOVQ, IMRMOVQ,
IOPQ, IJXX, ICALL, IRET, IPUSHQ, IPOPQ, IIADDQ, IJM };
# Does fetched instruction require a regid byte?
bool need_regids =
icode in { IRRMOVQ, IOPQ, IPUSHQ, IPOPQ,
IIRMOVQ, IRMMOVQ, IMRMOVQ, IIADDQ, IJM };
# Does fetched instruction require a constant word?
bool need_valC =
icode in { IIRMOVQ, IRMMOVQ, IMRMOVQ, IJXX, ICALL, IIADDQ, IJM };
################ Decode Stage ###################################
## What register should be used as the A source?
word srcA = [
icode in { IRRMOVQ, IRMMOVQ, IOPQ, IPUSHQ } : rA;
icode in { IPOPQ, IRET } : RRSP;
1 : RNONE; # Don't need register
];
## What register should be used as the B source?
word srcB = [
icode in { IOPQ, IRMMOVQ, IMRMOVQ, IIADDQ, IJM } : rB;
icode in { IPUSHQ, IPOPQ, ICALL, IRET } : RRSP;
1 : RNONE; # Don't need register
];
## What register should be used as the E destination?
word dstE = [
icode in { IRRMOVQ } && Cnd : rB;
icode in { IIRMOVQ, IOPQ, IIADDQ} : rB;
icode in { IPUSHQ, IPOPQ, ICALL, IRET } : RRSP;
1 : RNONE; # Don't write any register
];
## What register should be used as the M destination?
word dstM = [
icode in { IMRMOVQ, IPOPQ } : rA;
1 : RNONE; # Don't write any register
];
################ Execute Stage ###################################
## Select input A to ALU
word aluA = [
icode in { IRRMOVQ, IOPQ } : valA;
icode in { IIRMOVQ, IRMMOVQ, IMRMOVQ, IIADDQ, IJM } : valC;
icode in { ICALL, IPUSHQ } : -8;
icode in { IRET, IPOPQ } : 8;
# Other instructions don't need ALU
];
## Select input B to ALU
word aluB = [
icode in { IRMMOVQ, IMRMOVQ, IOPQ, ICALL,
IPUSHQ, IRET, IPOPQ, IIADDQ, IJM } : valB;
icode in { IRRMOVQ, IIRMOVQ } : 0;
# Other instructions don't need ALU
];
## Set the ALU function
word alufun = [
icode == IOPQ : ifun;
1 : ALUADD;
];
## Should the condition codes be updated?
bool set_cc = icode in { IOPQ, IIADDQ };
################ Memory Stage ###################################
## Set read control signal
bool mem_read = icode in { IMRMOVQ, IPOPQ, IRET, IJM };
## Set write control signal
bool mem_write = icode in { IRMMOVQ, IPUSHQ, ICALL };
## Select memory address
word mem_addr = [
icode in { IRMMOVQ, IPUSHQ, ICALL, IMRMOVQ, IJM } : valE;
icode in { IPOPQ, IRET } : valA;
# Other instructions don't need address
];
## Select memory input data
word mem_data = [
# Value from register
icode in { IRMMOVQ, IPUSHQ } : valA;
# Return PC
icode == ICALL : valP;
# Default: Don't write anything
];
## Determine instruction status
word Stat = [
imem_error || dmem_error : SADR;
!instr_valid: SINS;
icode == IHALT : SHLT;
1 : SAOK;
];
################ Program Counter Update ############################
## What address should instruction be fetched at
word new_pc = [
# Call. Use instruction constant
icode == ICALL : valC;
# Taken branch. Use instruction constant
icode == IJXX && Cnd : valC;
# Completion of RET instruction. Use value from stack
icode in { IRET, IJM } : valM;
# Default: Use incremented PC
1 : valP;
];
#/* $end seq-all-hcl */
执行:
(cd ../ptest; make SIM=../seq/ssim TFLAGS=-ij)
通过!
./optest.pl -s ../seq/ssim -ij
Simulating with ../seq/ssim
All 59 ISA Checks Succeed
./jtest.pl -s ../seq/ssim -ij
Simulating with ../seq/ssim
All 96 ISA Checks Succeed
./ctest.pl -s ../seq/ssim -ij
Simulating with ../seq/ssim
All 22 ISA Checks Succeed
./htest.pl -s ../seq/ssim -ij
Simulating with ../seq/ssim
All 756 ISA Checks Succeed
PartC
指令
需要在 sim/pipe 目录下执行各指令
有用指令包括:
检查长度是否超过 1000 Byte 的限制
../misc/yas ncopy.ys && ./check-len.pl < ncopy.yo
检查正确性
./correctness.pl
修改 pipe-full.hcl 后,重新构建模拟器
make clean; make psim VERSION=full
修改 ncopy.ys 后,检查 CPE、本地跑分
make drivers && ./benchmark.pl
优化思路
堪称本 Lab 最难的部分。
虽然 handout 中直接给出了直译版本的 ncopy,但是这显然没什么用:
# Function prologue.
# %rdi = src, %rsi = dst, %rdx = len
ncopy:
##################################################################
# You can modify this portion
# Loop header
xorq %rax,%rax # count = 0;
andq %rdx,%rdx # len <= 0?
jle Done # if so, goto Done:
Loop:
mrmovq (%rdi), %r10 # read val from src...
rmmovq %r10, (%rsi) # ...and store it to dst
andq %r10, %r10 # val <= 0?
jle Npos # if so, goto Npos:
irmovq $1, %r10
addq %r10, %rax # count++
Npos:
irmovq $1, %r10
subq %r10, %rdx # len--
irmovq $8, %r10
addq %r10, %rdi # src++
addq %r10, %rsi # dst++
andq %rdx,%rdx # len > 0?
jg Loop # if so, goto Loop:
##################################################################
# Do not modify the following section of code
# Function epilogue.
Done:
ret
直接测试发现其 CPE 高达 15.18,收获 0 分的好成绩!
Average CPE 15.18
Score 0.0/60.0
那么我们有什么办法来优化这个程序呢?
通过观察,我们发现此程序存在如下问题:
初始化置零了 rax,而这并不是必要的
每次循环都要判断 len 是否大于 0、更新起始地址 rdi 和目标地址 rsi,这非常耗时,可以使用循环展开来减少这些开销
Loop:
mrmovq (%rdi), %r10 # read val from src...
rmmovq %r10, (%rsi) # ...and store it to dst
andq %r10, %r10 # val <= 0?
jle Npos # if so, goto Npos:
irmovq $1, %r10
addq %r10, %rax # count++
Cookie: 0x11a67610
Touch1!: You called touch1()
Valid solution for level 1 with target ctarget
PASS: Sent exploit string to server to be validated.
NICE JOB!
Cookie: 0x11a67610
Touch2!: You called touch2(0x11a67610)
Valid solution for level 2 with target ctarget
PASS: Sent exploit string to server to be validated.
NICE JOB!
Phase 3
简单地翻译一下 hexmatch
首先分配一个长度为 110 的字符数组,然后再这 110 长度的数组中,随机选择一个小于 100 的起始位置 s
Cookie: 0x11a67610
Touch3!: You called touch3("11a67610")
Valid solution for level 3 with target ctarget
PASS: Sent exploit string to server to be validated.
NICE JOB!
Cookie: 0x11a67610
Touch2!: You called touch2(0x11a67610)
Valid solution for level 2 with target rtarget
PASS: Sent exploit string to server to be validated.
NICE JOB!
movq %rsp,%rax
ret
movq %rax,%rdi
ret
popq %rax
nop
ret
mov %eax,%ecx
nop
ret
mov %ecx,%edx
testb %al,%al
ret
mov %edx,%esi
and %dl,%dl
ret
leaq (%rdi, %rsi, 1),%rax
movq %rax,%rdi
ret
(有一些无效代码,这些代码是对照最终答案的字节码调整添加的)
运行:
gcc -c p5.s && objdump -d p5.o > p5.byte
得到 p5.byte 可以用以检验:
p5.o: file format elf64-x86-64
Disasm of section .text:
0000000000000000 <.text>:
0: 48 89 e0 mov %rsp,%rax
3: c3 ret
4: 48 89 c7 mov %rax,%rdi
7: c3 ret
8: 58 pop %rax
9: 90 nop
a: c3 ret
b: 89 c1 mov %eax,%ecx
d: 90 nop
e: c3 ret
f: 89 ca mov %ecx,%edx
11: 84 c0 test %al,%al
13: c3 ret
14: 89 d6 mov %edx,%esi
16: 20 d2 and %dl,%dl
18: c3 ret
19: 48 8d 04 37 lea (%rdi,%rsi,1),%rax
1d: 48 89 c7 mov %rax,%rdi
20: c3 ret
Cookie: 0x11a67610
Touch3!: You called touch3("11a67610")
Valid solution for level 3 with target rtarget
PASS: Sent exploit string to server to be validated.
NICE JOB!
Cookie: 0x11a67610
Touch3!: You called touch3("11a67610")
Valid solution for level 3 with target starget
PASS: Sent exploit string to server to be validated.
NICE JOB!
USER=Your student ID
PASS=Your password
URL=vpn.pku.edu.cn
OC_ARGS=--protocol=pulse
ID_CARD=Your ID card last 6 digits
PHONE_NUMBER=The 4th to 7th digits of your mobile phone number. e.g. 12345678910 -> 4567
感觉卫统就是,不注重上机教学,但又美名其曰“实践”强加一些代码作业,却完全忽略了大家的代码水平并不高(我自认算是编程比较强的那一批了,却全程感觉学习代码/写作业很迷惑),接着就是在 R 语言教学基础都没有的情况下直接给你上各种函数,这意味着有些常用语法什么的你可能要面对“见都没见过,但要求你写出来”的尴尬,而且我估计有些人可能直到最后连 q d p r 这些函数前缀是干啥的都得挨个去试…
回忆去年(似乎已经是前年了)修高数 C 时的场景,彼时的我就对高数颇有兴趣(因为这对我而言是最好学的一门课了,相对普生、普化而言),在开学初就常常抱着书提前老师的进度看,然后超前完成作业,有闲暇时间就看看蓝本/帮高中同学们解解题。
参加高数 C 期中考试时也因为考试前一天晚上刚好在蓝本上看到了考题于是顺风顺水以为能满分,结果因为一个小细节的符号喜提正态。但那时候的我并没有把这个失误放在心上(差一点就满了,这个结果已经足够我向很多朋友们炫耀了,那时的我就是这么想的),慢慢开始松懈,开始旷课,开始跟不上老师的进度,开始在 ddl 前卡线提交作业,开始连课本都看不完...
时光转眼来到期中考试前夕,我陷入了「黑屋洗衣服」的怪圈,高数 B 考试的范围有多离谱大家众所周知,我因为自己从来不听课更是焦虑的要死,看书看不进去、复习谢惠民只是在懊恼「哎呀这题当时看过怎么现在又不会做了!」、往年题刷了一些,均分 60...漫无目的的我打开了树洞,想着看看同学们怎么度过这段时间,没成想发现好多人在问题,我顿时觉得「帮帮同学们」或许是个不错的选择,于是就有了#4255209 里面的一堆题目解答。坦白地说,做这件事情的时候我也想过「为什么要帮我的竞争对手?」,但这个问题的答案也许就是我高中老师所说的那样「你们不是竞争者,而是一起互帮互助的同学」——曾经,我和高中同学们的目标是高考,那现在这个目标就是在数院这个大恶魔手里抢分(bushi
也许正是前一天晚上人品攒的太好了,我成功 AK 了第二天的期中考试,并且取得了我从来没在 P 大拿到的 100 分,要说那时候的我有多兴奋,那其实也不至于,短暂的兴奋过后其实更多是一种如释重负的感觉,「还好,还好,不枉我刷了这多谢惠民,考前熬了这么多个夜」
客观来说,这个满分其实吃了相当大的卷面优势——2022 年的期中是过往几年来最简单的一道题,而我又十分幸运地在考场上把最后一题和 xyt 助教习题课上提到的 $ \int_0^{\frac{\pi}{4}}{ln(1+tant)}dt $ 这道题联系起来了。但其实更戏剧性地是,当我把这套卷子拿给我的舍友看时,他却一眼看出这道题是前年高数 C 一学期的期末题(泊松积分#4257222),但很显然,我考高数 C 时没做出来,考高数 B 的时候也完全不记得了(sad...
期间,我也不是没有心生厌倦,我曾问过自己,就算花了这么久时间去写,最后能用上的又有多少人?我付出的时间精力难道不是自娱自乐吗?万一教学网也像编程网格一样更新了样式怎么办?... 但我却总是安慰自己,已经写了这么多了(沉没价值啊啊啊啊),怎么能忍心半途而废呢?于是,就在这种一边否定自己,一边问怀疑原有代码究竟是怎么写出来的,一边机械化的敲着已经用了数百遍的那些属性和变量的过程中,我还是渐渐磨出来了最终的成果——全新的、带黑暗模式的 PKU Art v2。