静态链接 HevSCGIHandler

在原来 HevSCGIServer 的设计中 HevSCGIHandler 接口的实现的整合形式为共享库,HevSCGIServerLibrary 会读取 modules.conf 配置文件,解析出 Handlers 配置,应用于 TaskDispatcher 中。最近由于一个特殊的需求,HevSCGIServerLibrary 和 HevSCGIHandler 都需要静态链接到主程序中,这在原来的设计中上不支持的。

分析发现 HevSCGIHandler 要做的就是定义一个类,主要是要实现了 HevSCGIHandler 接口,模块还实现一个导出函数 hev_scgi_handler_module_get_handler_type 注册与导出 GType。模块被 Library 加载的过程就是通过查找模块的 hev_scgi_handler_module_get_handler_type 函数,调用并得到 GType,然后实例化进增加到 TaskDispatcher。

在此基础上需要对 Library 和 Handler 实现做一些简单的修改就可以支持静态链接 Handler 实现了,首先对于 Library 要做的就是在实例化之前调用 Handler 实现的注册方法向 GType system 注册 GType,然后 Library 根据 modules.conf 中的 TypeName 从 GType system 找到 GType 再进行实例化,最后加入 TaskDispatcher;而对于 Handler 实现来说,就是要简单修改 G_DEFINE_TYPE_DYNAMIC… 为 G_DEFINE_TYPE,在 Makefile 中通过开关宏分离 static 与 shared 的实现。

diff --git a/src/hev-scgi-server.c b/src/hev-scgi-server.c
index ab2d9bb..99f0a40 100644
--- a/src/hev-scgi-server.c
+++ b/src/hev-scgi-server.c
@@ -20,6 +20,9 @@
 #include "hev-scgi-handler-module.h"
 #include "hev-scgi-handler-default.h"
 
+#include <hev-scgi-handler-cgi.h>
+#include <hev-scgi-handler-filebox.h>
+
 enum
 {
        PROP_0,
@@ -148,7 +151,8 @@ static void hev_scgi_server_constructed(GObject * obj)
          g_critical("%s:%d[%s]", __FILE__, __LINE__, __FUNCTION__);
 
        /* Register static handler types at here*/
-
+       HEV_REGISTER_TYPE_SCGI_HANDLER_CGI();
+       HEV_REGISTER_TYPE_SCGI_HANDLER_FILEBOX();
 
        module_dir_path = hev_scgi_config_get_module_dir_path(
                                HEV_SCGI_CONFIG(priv->scgi_config));
# modules.conf
 
[Module0]
Alias=HevSCGIHandlerFilebox
Pattern=^/fb(.*)$
TypeName=HevSCGIHandlerFilebox
 
BaseURI=/fb/
CleanInterval=600
FilePoolPath=/tmp
FileMetaPath=/tmp
FileTempPath=/tmp
 
[Module1]
Alias=HevSCGIHandlerCGI
Pattern=^/cgi((/|\\?)(.*))?$
TypeName=HevSCGIHandlerCGI
 
CGIBinPath=/tmp/test.cgi
WorkDir=/tmp

最后,只要在主程序中将 library 与 handler 实现全部静态链接就可以了。

HevSocks5Service – 简单的 Socks5 服务

HevSocks5Service 是基于 GLib/GObject/GIO 的简单 Socks5 服务实现,参考 RFC1928。初步不支持任何认证且仅支持 CONNECT 命令,采用异步+多线程模式。

在 HevSocks5Service 的 Splice 实现中仍然没有直接使用 GIOStream 的 splice_async 方法,原因和之前在 HevTLSTunnel 中遇到的问题一样,GIO 库中 GTask 的线程池最大并发线程数被硬编码限制为了 10 个,从而使异步方法的并发数限制在一定的值以下。虽然不知道这样设计的目的何在,但这已经影响了对相关的异步方法的调用。

另外,通过对比使用 GIO 和直接使用系统原生 API,感觉基于 GObject 的实现对资源的使用相比较多,库的层层抽象及设计的不合理也使得实现不灵活,或也只能不按套路走。是不是我应该不要再过多的“依赖” GLib 及相关的库了?

Over!

修复 gspawn-win32-helper 获取参数失败

使用 MinGW 中的 GCC 4.8.1 编译 GLib 2.38.1,执行 valac 编译会报如下错误:

ERROR:gspawn-win32-helper.c:226:WinMain: assertion failed: (__argc >=
ARG_COUNT)

阅读代码 glib/gspawn-win32-helper.c 发现此程序依赖于 __argc 和 __argv 全局变量进行参数访问,但有相关说明这两个全局变量是由 Microsoft 编译和 msvcrt.dll 提供,调试发现使用 MinGW 编译的 gspawn-win32-helper.exe 无论是否在命令行下进行都会导致上述错误,而且打印发现 __argc 永远是 0。

查看 /mingw/include/stdlib.h 会发现还有一组访问参数相关的全局变量 _argc 和 _argv,验证后是可行的。解决问题,下面是补丁文件:

From ebe6e6989b49bd119acc2d29b2f8ad0b6bb03313 Mon Sep 17 00:00:00 2001
From: Heiher <root@heiher.info>
Date: Wed, 30 Oct 2013 22:17:29 +0800
Subject: [PATCH] glib/gspawn-win32-helper.c: Fix get arguments for GCC
 compiler.
 
---
 glib/gspawn-win32-helper.c | 61 ++++++++++++++++++++++++++--------------------
 1 file changed, 34 insertions(+), 27 deletions(-)
 
diff --git a/glib/gspawn-win32-helper.c b/glib/gspawn-win32-helper.c
index 7896941..8d2bcbd 100644
--- a/glib/gspawn-win32-helper.c
+++ b/glib/gspawn-win32-helper.c
@@ -222,28 +222,35 @@ main (int ignored_argc, char **ignored_argv)
   _CrtSetReportMode(_CRT_ASSERT, 0);
 #endif
 
+#ifdef _MSC_VER // for Microsoft compiler
+#define __ARGC __argc
+#define __ARGV __argv
+#else           // for GCC compiler (MinGW)
+#define __ARGC _argc
+#define __ARGV _argv
+#endif
 
-  g_assert (__argc >= ARG_COUNT);
+  g_assert (__ARGC >= ARG_COUNT);
 
   /* Fetch the wide-char argument vector */
   __wgetmainargs (&argc, &wargv, &wenvp, 0, &si);
 
-  /* We still have the system codepage args in __argv. We can look
+  /* We still have the system codepage args in __ARGV. We can look
    * at the first args in which gspawn-win32.c passes us flags and
-   * fd numbers in __argv, as we know those are just ASCII anyway.
+   * fd numbers in __ARGV, as we know those are just ASCII anyway.
    */
-  g_assert (argc == __argc);
+  g_assert (argc == __ARGC);
 
   /* argv[ARG_CHILD_ERR_REPORT] is the file descriptor number onto
    * which write error messages.
    */
-  child_err_report_fd = atoi (__argv[ARG_CHILD_ERR_REPORT]);
+  child_err_report_fd = atoi (__ARGV[ARG_CHILD_ERR_REPORT]);
 
   /* Hack to implement G_SPAWN_FILE_AND_ARGV_ZERO. If
    * argv[ARG_CHILD_ERR_REPORT] is suffixed with a '#' it means we get
    * the program to run and its argv[0] separately.
    */
-  if (__argv[ARG_CHILD_ERR_REPORT][strlen (__argv[ARG_CHILD_ERR_REPORT]) - 1] == '#')
+  if (__ARGV[ARG_CHILD_ERR_REPORT][strlen (__ARGV[ARG_CHILD_ERR_REPORT]) - 1] == '#')
     argv_zero_offset++;
 
   /* argv[ARG_HELPER_SYNC] is the file descriptor number we read a
@@ -252,16 +259,16 @@ main (int ignored_argc, char **ignored_argv)
    * duplicate the process handle we sent it. Duplicating a handle
    * from another process works only if that other process exists.
    */
-  helper_sync_fd = atoi (__argv[ARG_HELPER_SYNC]);
+  helper_sync_fd = atoi (__ARGV[ARG_HELPER_SYNC]);
 
   /* argv[ARG_STDIN..ARG_STDERR] are the file descriptor numbers that
    * should be dup2'd to 0, 1 and 2. '-' if the corresponding fd
    * should be left alone, and 'z' if it should be connected to the
    * bit bucket NUL:.
    */
-  if (__argv[ARG_STDIN][0] == '-')
+  if (__ARGV[ARG_STDIN][0] == '-')
     ; /* Nothing */
-  else if (__argv[ARG_STDIN][0] == 'z')
+  else if (__ARGV[ARG_STDIN][0] == 'z')
     {
       fd = open ("NUL:", O_RDONLY);
       if (fd != 0)
@@ -272,7 +279,7 @@ main (int ignored_argc, char **ignored_argv)
     }
   else
     {
-      fd = atoi (__argv[ARG_STDIN]);
+      fd = atoi (__ARGV[ARG_STDIN]);
       if (fd != 0)
 	{
 	  dup2 (fd, 0);
@@ -280,9 +287,9 @@ main (int ignored_argc, char **ignored_argv)
 	}
     }
 
-  if (__argv[ARG_STDOUT][0] == '-')
+  if (__ARGV[ARG_STDOUT][0] == '-')
     ; /* Nothing */
-  else if (__argv[ARG_STDOUT][0] == 'z')
+  else if (__ARGV[ARG_STDOUT][0] == 'z')
     {
       fd = open ("NUL:", O_WRONLY);
       if (fd != 1)
@@ -293,7 +300,7 @@ main (int ignored_argc, char **ignored_argv)
     }
   else
     {
-      fd = atoi (__argv[ARG_STDOUT]);
+      fd = atoi (__ARGV[ARG_STDOUT]);
       if (fd != 1)
 	{
 	  dup2 (fd, 1);
@@ -301,9 +308,9 @@ main (int ignored_argc, char **ignored_argv)
 	}
     }
 
-  if (__argv[ARG_STDERR][0] == '-')
+  if (__ARGV[ARG_STDERR][0] == '-')
     ; /* Nothing */
-  else if (__argv[ARG_STDERR][0] == 'z')
+  else if (__ARGV[ARG_STDERR][0] == 'z')
     {
       fd = open ("NUL:", O_WRONLY);
       if (fd != 2)
@@ -314,7 +321,7 @@ main (int ignored_argc, char **ignored_argv)
     }
   else
     {
-      fd = atoi (__argv[ARG_STDERR]);
+      fd = atoi (__ARGV[ARG_STDERR]);
       if (fd != 2)
 	{
 	  dup2 (fd, 2);
@@ -322,19 +329,19 @@ main (int ignored_argc, char **ignored_argv)
 	}
     }
 
-  /* __argv[ARG_WORKING_DIRECTORY] is the directory in which to run the
+  /* __ARGV[ARG_WORKING_DIRECTORY] is the directory in which to run the
    * process.  If "-", don't change directory.
    */
-  if (__argv[ARG_WORKING_DIRECTORY][0] == '-' &&
-      __argv[ARG_WORKING_DIRECTORY][1] == 0)
+  if (__ARGV[ARG_WORKING_DIRECTORY][0] == '-' &&
+      __ARGV[ARG_WORKING_DIRECTORY][1] == 0)
     ; /* Nothing */
   else if (_wchdir (wargv[ARG_WORKING_DIRECTORY]) < 0)
     write_err_and_exit (child_err_report_fd, CHILD_CHDIR_FAILED);
 
-  /* __argv[ARG_CLOSE_DESCRIPTORS] is "y" if file descriptors from 3
+  /* __ARGV[ARG_CLOSE_DESCRIPTORS] is "y" if file descriptors from 3
    *  upwards should be closed
    */
-  if (__argv[ARG_CLOSE_DESCRIPTORS][0] == 'y')
+  if (__ARGV[ARG_CLOSE_DESCRIPTORS][0] == 'y')
     for (i = 3; i < 1000; i++)	/* FIXME real limit? */
       if (i != child_err_report_fd && i != helper_sync_fd)
         if (_get_osfhandle (i) != -1)
@@ -360,16 +367,16 @@ main (int ignored_argc, char **ignored_argv)
   child_err_report_fd = dup_noninherited (child_err_report_fd, _O_WRONLY);
   helper_sync_fd = dup_noninherited (helper_sync_fd, _O_RDONLY);
 
-  /* __argv[ARG_WAIT] is "w" to wait for the program to exit */
-  if (__argv[ARG_WAIT][0] == 'w')
+  /* __ARGV[ARG_WAIT] is "w" to wait for the program to exit */
+  if (__ARGV[ARG_WAIT][0] == 'w')
     mode = P_WAIT;
   else
     mode = P_NOWAIT;
 
-  /* __argv[ARG_USE_PATH] is "y" to use PATH, otherwise not */
+  /* __ARGV[ARG_USE_PATH] is "y" to use PATH, otherwise not */
 
-  /* __argv[ARG_PROGRAM] is executable file to run,
-   * __argv[argv_zero_offset]... is its argv. argv_zero_offset equals
+  /* __ARGV[ARG_PROGRAM] is executable file to run,
+   * __ARGV[argv_zero_offset]... is its argv. argv_zero_offset equals
    * ARG_PROGRAM unless G_SPAWN_FILE_AND_ARGV_ZERO was used, in which
    * case we have a separate executable name and argv[0].
    */
@@ -379,7 +386,7 @@ main (int ignored_argc, char **ignored_argv)
    */
   protect_wargv (wargv + argv_zero_offset, &new_wargv);
 
-  if (__argv[ARG_USE_PATH][0] == 'y')
+  if (__ARGV[ARG_USE_PATH][0] == 'y')
     handle = _wspawnvp (mode, wargv[ARG_PROGRAM], (const wchar_t **) new_wargv);
   else
     handle = _wspawnv (mode, wargv[ARG_PROGRAM], (const wchar_t **) new_wargv);
-- 
1.8.4.2

Over!

Mozilla JavaScript 的 MIPS N32 ABI 支持补丁

Mozilla JavaScript 17.0 已经合并了 MIPS 公司开发的 MIPS 相关 JIT 补丁,仅支持 O32 ABI 的,此补丁用于增加 N32 ABI 的支持。

Mozilla JavaScript 官方版本:http://ftp.mozilla.org/pub/mozilla.org/js/mozjs17.0.0.tar.gz
N32 ABI 支持补丁:http://mirrors.heiher.info/sources/extra/js/

jit-test

python2 ./jit_test.py ../js17                                                                                                         
[4876|   0|   0|4876]    100% =======================================>|  466.9s
PASSED ALL

v8 benchmark

../js17 -m -n -f run.js
Richards: 838
DeltaBlue: 1063
Crypto: 1098
RayTrace: 330
EarleyBoyer: 991
RegExp: 128
Splay: 1182
NavierStokes: 732
----
Score (version 7): 659

Over!

GObject 子类重载基类实现的接口方法

GObject 对象系统中,基类A如实现了接口类I并且实现了I的三方法 I::a, I::b, I::c 后。基于类A的派生出的子类B如想重载 I::c 方法,只需要重新实现一下接口类 I,在接口类的初始化方法中仅仅重载 I::c 方法即可,而基类已经实现的 I::a, I::b 则依旧可用,而不是没有实现的状态。

Over!

GLib/GIO 事件源、异步方法与线程的关系

事件源与线程的关系
事件源可以在任意线程中创建并可以 Attach 到任意的线程主上下文(GMainContext)上,Callback 由 Attach 的主线程上下文对应的线程调用。

异步方法与线程的关系
在 GIO 中,一个异步方法通常有两个类型的方法,分别是以 _async 和 _finish 结尾的。_async 方法用于发起一个异步调用,而 _finish 用于取得异步调用的结果。异步方法是使用线程实现的,大概的逻辑是一个线程在 _async 方法中创建新的线程,新线程执行一个事务,完成后通过一定的方法将结果通知给调用线程,调用线程再调用 _finish 方法取得异步方法的结果。

在 GLib 系统中,每个线程都有其主上下文(GMainContext),在一个线程中(主线程或其它线程)调用对象 _async 方法后,会创建一个 GSimpleAsyncResult 类型的对象,该对象用于保存和在调用线程与执行线程间传递数据。接着将方法调用需要的数据(即 _async 方法的参数)“打包“在一个结构体的实例中并设置到 GSimpleAsyncResult 的对象中保存。然后,会调用 g_simple_async_result_run_in_thread 方法并传入一个线程处理函数,该方法通过 GIOSchedulerJob 创建一个新的线程(通过一个专门的线程池),并执行传入的线程处理函数。当线程处理函数执行完成后,会将结果数据保存在 GSimpleAsyncResult 对象中(线程处理函数中实现)。接着会创建一个 GIldeSource 并 Attach 到调用 _async 方法的线程主上下文中,此事件源的 Callback 中会调用 _async 方法中传入的 GAsyncReadyCallback(由调用 _async 方法的线程调用)。最后,应该在 GAsyncReadyCallback 中调用对象的 _finish 方法取得异步调用的结果。

测试程序

/* async.c
 * Heiher <admin@heiher.info>
 * gcc -o async async.c `pkg-config --cflags --libs gio-2.0`
 */
 
#include <gio/gio.h>
 
void enumerate_children_handler (GObject *obj,
			GAsyncResult *res, gpointer user_data)
{
	GFileEnumerator *enumerator = NULL;
 
	enumerator = g_file_enumerate_children_finish (G_FILE (obj),
				res, NULL);
	g_file_enumerator_close_async (enumerator, G_PRIORITY_DEFAULT,
				NULL, NULL, NULL);
	g_object_unref (enumerator);
 
	g_debug ("Handler Thread: %p", g_thread_self ());
}
 
gboolean source_handler (gpointer user_data)
{
	g_debug ("Source Thread: %p", g_thread_self ());
 
	return FALSE;
}
 
gpointer thread_handler (gpointer data)
{
	GMainContext *context = data;
	GSource *source = NULL;
 
	g_debug ("Test Thread: %p", g_thread_self ());
 
	source = g_idle_source_new ();
	g_source_set_priority (source, G_PRIORITY_DEFAULT);
	g_source_set_callback (source, source_handler, NULL, NULL);
	g_source_attach (source, context);
	g_source_unref (source);
 
	return FALSE;
}
 
int main (int argc, char *argv[])
{
	GMainLoop *main_loop = NULL;
 
	g_type_init ();
 
	main_loop = g_main_loop_new (NULL, FALSE);
	if (main_loop) {
		GFile *file = NULL;
 
		file = g_file_new_for_path ("/usr/lib");
 
		g_debug ("Main Thread: %p", g_thread_self ());
 
		g_file_enumerate_children_async (file,
					G_FILE_ATTRIBUTE_STANDARD_NAME,
					G_FILE_QUERY_INFO_NONE,
					G_PRIORITY_DEFAULT,
					NULL,
					enumerate_children_handler,
					NULL);
 
		g_thread_new ("Test", thread_handler,
					g_main_context_default ());
 
		g_main_loop_run (main_loop);
 
		g_object_unref (file);
 
		g_main_loop_unref (main_loop);
	}
 
	return 0;
}

运行结果

** (process:22820): DEBUG: Main Thread: 0x1aa4ca0
** (process:22820): DEBUG: Test Thread: 0x1adac50
** (process:22820): DEBUG: Source Thread: 0x1aa4ca0
** (process:22820): DEBUG: Handler Thread: 0x1aa4ca0

Over!

GtkStatusIcon 弹出菜单问题记录

GtkStatusIcon 使用教程 => https://live.gnome.org/GtkStatusIconTutorial

问题描述
将 gnome panel 位置调整到“底部”后,弹出的菜单位置不正确,导致菜单项不可见及无法选择。

问题原因

gtk_menu_popup(GTK_MENU(my_menu),
                        NULL,
                        NULL,
                        gtk_status_icon_position_menu,
                        status_icon,
                        button,
                        activate_time);
 
gtk_widget_show_all(my_menu);  // 在 popup 后才进行 show,导致窗口大小计算错误。

在调用 gtk_menu_popup 之前需要对 menu 进行 show。

Over!

Linux 平台一种进程代码注入方法

用于在目标程序的 main 函数执行前完成一些操作 😉 特定情况下用来调试还是不错的。

源代码

/* fakemain.c
 * Heiher <admin@heiher.info>
 */
 
#include <stdio.h>
 
#define __USE_GNU
#include <dlfcn.h>
 
static void do_something(void)
{
	printf("Hello!\n");
}
 
int __libc_start_main(int (*main)(int, char **, char **),
			int argc, char **ubp_av, void (*init)(void),
			void (*fini)(void), void (*rtld_fini)(void),
			void (*stack_end))
{
	int (*__libc_start_main_real)(int (*main) (int, char **, char **),
				int argc, char **ubp_av, void (*init)(void),
				void (*fini)(void), void (*rtld_fini)(void),
				void (*stack_end));
 
	do_something();
 
	__libc_start_main_real = dlsym(RTLD_NEXT, "__libc_start_main");
 
	return __libc_start_main_real(main, argc, ubp_av, init, fini,
				rtld_fini, stack_end);
}

编译

gcc -o libfakemain.so -fPIC -shared fakemain.c -ldl

测试

LD_PRELOAD=./libfakemain.so ls
Hello!
fakemain.c  hotkey  hotkey.vala  libfakemain.so

Over!

HevSCGIServer 混合语言开发

HevSCGIServerLibrary 实现了 GObject Introspection 支持且生成了 Vala API。从此,HevSCGIServer 框架支持混合语言编程了,理论上只要支持 GObject Introspection 的语言都可以用来开发。

目前,存在一个 Vala 语言编写的实例程序 HevSCGIServerVala,功能与 C 语言实现的 HevSCGIServerHello 一样。

下一步将通过模块的形式实现一个 JavaScript 语言的解释器,将意味着 Web Application 可以使用 JavaScript 语言编写,要注意的是它可以使用几乎所有的系统库哦。(Gjs 通过 GObject Introspection 可调用几乎所有的库)

Over!