用 xlang 写一个在线反汇编的网站
xlang2025-04-13 17:29:38前段时间在网上看见这么一个网站:

在左边输入C/C++的源代码,右侧就能显示它的反汇编代码,今天用 xlang 来实现一下。
先分析一下,整理下思路,最简单的实现就是发送源码到服务器上,再由服务器上的gcc 或者 clang等进行编译/反编译,最后将结果传回客户端。
为了体验能更好一点,把WebSocket 用上,再细化一下:
- 第一步,访问前端页面,建立起对应的 WebSocket 链接。
- 第一步,前端通过 POST请求,发送源代码和操作请求到服务器。
- 第二步,服务器编译源代码,将结果通过WebSocket传回。
接下来用 xlang 来实现:
首先打开 XStudio 创建一个XSP动态网站项目:

建立好之后编译, 可能提示如下错误:
xweb_rt.lix(行:47, 字:26, :27) [Error]错误:
找不到对象:getNetWorkMode
在以下对象中获取属性:
1:对象:__controller = 类型:ServerController [/ServerController] private (私有)
xweb_rt.lix(行:4, 字:2)
--> __controller.getNetWorkMode
原因是建立项目可能使用了旧的模板,打开源代码文件 OnlineDisassembler.x ,在 ServerController 类中添加一个方法 getNetWorkMode , 如下:
class ServerController {
public String getServerName() {
return "XLANGSampleServer";
}
public String getServerDisplayName() {
return "XLANGSampleServer";
}
public String getServerDescription() {
return "xlang web server";
}
public void serverInit (Website wb) {
wb.addDefaultPage ("index.xsp");
}
public int getServerPort() {
return 1808;
}
public int getThreadPoolSize() {
return 4;
}
public void onServerStart (HttpServer w) {
}
// 添加这个方法, 返回服务器工作的网络模式, 默认网络模式 + WEBSOCKET启用 (ALLOW_UPGRADE表示接受连接升级为Websocket)
public int getNetWorkMode() {
return HttpServer.DEFAULT_MODEL | HttpServer.ALLOW_UPGRADE;
}
public void startResult (bool b) {
}
public String getDomain() {
return "*;127.0.0.1;localhost";
}
};
接下来,在 serverInit 方法里面设置服务器相关的一些参数, 比如设置 wwwroot 目录,设置网站的根目录,后面会把用到的静态文件放到这个目录下。
public void serverInit (Website wb) {
wb.addDefaultPage ("index.xsp");
// 开启静态文件文件缓存, 提高服务器性能
wb.configCacher(true, 1024, 1024*1024);
// 设置网站根目录到程序所在的目录下的wwwroot目录
wb.setRootDirectory(_system_.getAppDirectory().appendPath("wwwroot"));
}
F7 编译一下,按 Ctrl + Alt + O 打开程序所在目录, 并在其中新建 wwwroot 目录。
然后还要建立一个 Servlet 来响应前端的POST请求和WebSocket操作。
点击菜单 编辑 -> 添加 -> 添加对象 来添加一个类,起名为 Editor 吧.

打开源码 Editor.x 编辑源码, 使这个类继承 HttpServlet ,并添加构造方法
//继承 HttpServlet ,使能响应 Http 请求
class Editor : public HttpServlet {
public Editor() {
//super 的构造中开启对 WebSocketServlet 和 HTTPServlet 的支持
//地址映射为 editor.html,注意前面的路径斜杠不能少, 这里要填的是匹配editor.html的正则表达式, 所以是 editor\.html
super (HttpServlet.FLAG_WEBSOCKETLET | HttpServlet.FLAG_SERVLET, "/editor\.html");
}
};
接下来继续在Editor中添加 doGet 和 doPost 方法,这两个方法用于处理 get请求和post请求。
为了能处理 WebSocket 还要继续添加 onMessage 和 onClose 方法。
// 处理 get 请求
public void doGet (HttpServletRequest request, HttpServletResponse respons) override{
}
// 处理 post 请求
public void doPost (HttpServletRequest request, HttpServletResponse respons) override{
}
// websocket消息
public void onMessage (WebSocketSession session, String message) override {
}
// websocket关闭的事件
public void onClose (WebSocketSession session, String rason) override {
}
接下来我们的思路是在get请求访问这个页面的时候,生成一个随机字符串,随页面下发
客户端收到之后通过websocket将这个唯一字符串传回,建立起websocket会话,方便后期工作。
然后我们的前端还需要一个 html 模板,用于响应get请求,在项目根目录下新建data目录,在目录中建立 ws.html 文件:
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<script type="text/javascript">
//用于存放唯一id 随机字符串
var sessionId = "%SESSIONID%";
var url = window.location.href;
var pro = url.indexOf("://");
// 使用ws协议连接当前页面地址
var ws = new WebSocket("ws" + url.substring(pro));
// websocket的建立连接的事件
ws.onopen = function() {
ws.send(sessionId);//连接建立之后 将唯一传回给服务器
};
//消息事件
ws.onmessage = function(evt) {
};
//连接关闭事件
ws.onclose = function() {
// 关闭 websocket
output.value = output.value + ("\n连接已关闭...");
};
</script>
</body>
</html>
继续返回XStudio,在 Editor 类中把这个html页面打包(代码中使用__xPackageResource可以打包资源为byte数组),并添加一个产生随机字符串的静态方法,
并在doGet的时候将html模板中的%SESSIONID% 替换成随机码并返回给客户端:
//将模板资源打包为 byte 数组
static byte [] template = __xPackageResource ("./data/ws.html");
// 产生随机字符串id
public static String randomHexString (int n) {
static const byte [] table = "0123456789abcdef".getBytes();
byte [] code = new byte[n];
for (int i : n) {
code[i] = table[ (int) (Math.random() * table.length)];
}
return new String (code);
}
public void doGet (HttpServletRequest request, HttpServletResponse respons) {
//产生一个 16位的随机码
// 将模板中的 %SESSIONID% 替换为随机码,然后将模板内容返回给客户端
// 原则上还要判断是否唯一,这里简单处理
respons.print (template.replace ("%SESSIONID%", randomHexString (16)) );
}
websocket 相关操作:
// 添加一个map变量,用于保存id对应的websocketsession,方便根据id找到需要回传的websocketsession对象
static Map<String, WebSocketSession> outputCache = new Map<String, WebSocketSession>();
// 处理 websocket的回传消息,上一步html的websocket的建立连接事件会回传id,服务端在这里会收到
public void onMessage (WebSocketSession session, String message) override {
if (message.length() == 16 && session.getUserData() == nilptr) { //判断id长度是否合法,并且该session是否已经绑定了id
session.setUserData (message); //绑定用户数据为id,以免重复绑定
synchronized (outputCache) {
outputCache.put (message, session); // 将websocketsession 与对应的id保存到map中
}
}
}
// 如果客户端关闭了websocket连接,则要从map中清理资源
public void onClose (WebSocketSession session, String rason) override {
try {
String sid = (String) session.getUserData();
if (sid != nilptr) {
synchronized (outputCache) {
outputCache.remove (sid);
}
}
} catch (Exception e) {
}
}
继续在 html 前端页面中添加内容,找个编辑器控件,就用CodeMirror吧,然后添加按钮。
添加按钮事件,和通过POST发送源代码给上面的 Servlet 的操作,为了操作方便,用 jquery 来发送请求。
//html中添加按钮事件
<button onclick="doCompiling()" style="background-color: #2879FE; width: 70px;">编译</button>
// js中写入发送请求的操作
function request(method, url, data, fn) {
var _request = {};
_request.type = method;
_request.url = url;
_request.dataType = "json";
if (data != null) {
_request.data = data;
}
if (fn != null) {
_request.success = fn;
}
$.ajax(_request);
}
function doCompiling() {
var data = {
source: editor.getValue(), // edito 是源代码编辑控件
id: sessionId // 把sessionid带上,让服务器知道结果该回传到哪个websocketsession
};
request('post', 'editor.html', data, function(resp) { //发送请求
});
}
接下来在服务端处理这个 post请求,到doPost方法中 ,使用getParam 分别取出 上面发送的 source和id,
source 就是发送的源码,id 就是此会话对应的 随机id值。
取出数据后需要核对 id 是否有效,然后将source保存为文件,调用 gcc 进行编译。
编译结束后调用objdump进行反汇编,再将结果返回客户端
为了方便处理文件操作,点击菜单 工具->包管理,把 FileStream 包添加到项目中

然后继续编辑代码
// 处理 post 请求
public void doPost (HttpServletRequest request, HttpServletResponse respons) override {
String src = request.getParam ("source"); // 取出 source
String Id = request.getParam ("id"); //取出 id
compile (Id, src);
}
static bool compile (String id, String content) {
WebSocketSession session = nilptr;
synchronized (outputCache) {
session = outputCache.get (id);
}
if (session != nilptr) { // 如果id有效
FileStream.FileOutputStream fos = nilptr;
//构造临时文件
File fn = new File (_system_.getAppDirectory().appendPath ("temp").appendPath (id + ".cpp") );
//构造目标文件
File fd = new File (fn.getPath().replaceExtension (".o") );
if (fd.exists() ) { // 如果目标文件已存在 则删除
fd.remove();
}
//使用一个stringbuffer对象储存gcc的输出结果
StringBuffer cache = new StringBuffer();
try {
// 将内容写入临时文件
fos = new FileStream.FileOutputStream (fn);
fos.write (content.getBytes(), 0, content.length() );
fos.close();//记得关闭
fos = nilptr;
// 调用 gcc 将文件编译到 .o文件
Process process = new Process ("/usr/bin/gcc", new String[] {"gcc", "-c", fn.getPath(), "-o", fd.getPath() });
// 启动进程,并重定向标准输出和标准错误到输出
if (process.create (Process.StdOut | Process.RedirectStdErr) ) {
byte [] line = new byte[1024];
int rd = 0;
// 按行读取gcc的输出内容
while ( (rd = process.read (line, 0, 1024) ) > 0) {
cache.append (line, 0, rd);
int szline = cache.indexOf ('\n');
if (szline != -1) {
String msg = cache.substring (0, szline + 1);
cache.replace (0, szline + 1, "");
// 将读取到的行通过websocket发送给客户端
if (!session.sendText (msg) ) {
_system_.output ("not send"); //发送失败提示
}
}
}
}
} catch (Exception e) {
} finally {
if (fos != nilptr) { //在finally中判断文件是否已经关闭,如果中途出现异常,则要清理资源,否则会等到gc执行时才能被清理,造成长时间占用资源
fos.close();
}
}
if (cache.length() > 0) {
if (!session.sendText (cache.toString() ) ) {
_system_.output ("not send");
}
}
if (fd.exists() ) { // 如果目标文件已生成
disassemble (session, id, fd.getPath() ); //调用反编译
session.sendText ("完成...\n");
} else {
session.sendText ("编译失败...\n");
}
return true;
}
return false;
}
public static void disassemble (WebSocketSession session, String id, String path) {
// 调用objdump 反汇编目标文件,并将输出结果通过session 返回给客户端
// 与上面操作类似 ...
}
完成之后,就可以找个 linux 服务器进行测试了
(如果使用的是windows的话gcc路径需要更改,但Windows的编码在使用websocket发送前需要进行转换为utf8,见完整源代码)
在项目属性中把目标操作系统和架构改成适合linux服务器运行的参数

构建之后,将目标文件连同 wwwroot 目录整个上传,在服务器上运行./OnlineDisassembler 就可以使用客户端进行访问了。
PS:如果需要远程调试,则需要加 -xdbn:23326 参数,23326是端口号,可以改成任意可用的端口号,例如
以调试模式运行,远程调试端口为 23327
./OnlineDisassembler -xdbn:23327
然后在 XStudio 的菜单调试->远程调试中填入IP和端口号


点击确定,就可以进行远程调试,远程调试连接后,程序会在入口处停止,按F5直接运行。
打开浏览器,输入服务器地址和端口,http://192.168.240.128:1808 (对应ServerController 类中填写的端口号)查看效果

找点 C++ 代码测试,可以正常输出。

为了支持更多的架构和优化参数,在这个基础增加点东西,把clang 也加进去,顺便把 x86 arm 和 aarch64 架构也加进去(服务器上需要安装对应的gcc和objdump工具),再把各种优化选项添加进去,经过一番改造...

最后为了降低服务器负荷,把提交频率限制在5秒钟内只能提交一次,就可以放上服务器了。
完整的源代码下载:
此项目需要 XStudio2.0 打开, xlang 5.0构建。
上一篇:xlang5.0更新说明下一篇:xlang5.1更新说明

