System V AMD64 ABI calling conventions

The calling convention of the System V AMD64 ABI is followed on Solaris, Linux, FreeBSD, Mac OS X, and other UNIX-like or POSIX-compliant operating systems. The first six integer or pointer arguments are passed in registers RDI, RSI, RDX, RCX, R8, and R9, while XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6 and XMM7 are used for floating point arguments. For system calls, R10 is used instead of RCX. As in the Microsoft x64 calling convention, additional arguments are passed on the stack and the return value is stored in RAX.

Registers RBP, RBX, and R12-R15 are callee-save registers; all others must be saved by the caller if they wish to preserve their values.

Unlike the Microsoft calling convention, a shadow space is not provided; on function entry, the return address is adjacent to the seventh integer argument on the stack.

Over!

Linux 不使用 chroot 临时替换C运行时库

有些时候我们需要在老旧的 Linux 系统上运行一些依赖于较新版本 C 库的应用程序或库,应用程序会因为系统中安装的C库缺少符号还启动失败。解决方法之一就是临时替换使用非系统安装的C运行时库。使用临时C库需要做些什么配置及会带来哪些问题呢?

配置步骤
1. 下载与目标应用程序版本相匹配的临时C库,解压缩到临时位置 A。
2. 需要设置 LD_LIBRARY_PATH 环境变量指向目标临时C库的存储位置 A。
3. 需要通过与临时C库匹配的 ld.so 启用应用程序。因为应用程序默认是链接了一个绝对路径的 ld.so,如 x86_64 是 /lib64/ld-linux-x86-64.so.2

衍生问题
使用临时C库的 ld.so 启动的应用程序执行系统标准命令的子进程出错,原因是因为环境变量 LD_LIBRARY_PATH 被子进程继承,从而导致子进程在执行系统C库的ld.so中加载了版本不匹配的临时C库。

解决办法
在合适的时机清除环境变量 LD_LIBRARY_PATH,最合适的时机应用就是执行目标应用程序 main 函数之前啦。这里又要用到了之前写过的方法 => Linux 平台一种进程代码注入方法

/* fakemain.c
 * Heiher <admin@heiher.info>
 */
 
#include <stdio.h>
#include <stdlib.h>
 
#define __USE_GNU
#include <dlfcn.h>
 
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));
 
	unsetenv ("LD_PRELOAD");
	unsetenv ("LD_LIBRARY_PATH");
 
	__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 -fPIC -O3 -shared -o libfakemain.so fakemain.c -ldl

设置环境变量 LD_PRELOAD=/xxx/libfakemain.so,运行目标应用程序在执行 main 之前即会清除 LD_PRELOAD 和 LD_LIBRARY_PATH 变量。

为了方便使用我还写了个 wrapper,使用方法是将真实的目标应用程序 xxx 重命令为 xxx.bin,然后创建个符号链接 xxx 指向 wrapper,执行时直接执行 xxx,wrapper 会自动设置所需要的环境变量。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
 
int
main (int argc, char *argv[])
{
	int i;
	char buf[1024], path[1024];
	char *str, *root, *args[512];
 
	/* get FAKE_ROOT */
	root = getenv ("FAKE_ROOT");
	if (!root) {
		fprintf (stderr, "Please set environment FAKE_ROOT!\n");
		return -1;
	}
 
	/* export PATH */
	str = getenv ("PATH");
	if (!str) {
		fprintf (stderr, "Get environment PATH failed!\n");
		return -2;
	}
	if (NULL == strstr (str, root)) {
		strcpy (buf, root);
		strcat (buf, "/bin:");
		strcat (buf, str);
		if (0 != setenv ("PATH", buf, 1)) {
			fprintf (stderr, "Set environment PATH failed!\n");
			return -3;
		}
	}
 
	/* export LD_PRELOAD */
	strcpy (buf, root);
	strcat (buf, "/lib64/libfakemain.so");
	if (0 != setenv ("LD_PRELOAD", buf, 1)) {
		fprintf (stderr, "Set environment LD_PRELOAD failed!\n");
		return -4;
	}
 
	/* export LD_LIBRARY_PATH */
	strcpy (buf, root);
	strcat (buf, "/lib64");
	if (0 != setenv ("LD_LIBRARY_PATH", buf, 1)) {
		fprintf (stderr, "Set environment LD_LIBRARY_PATH failed!\n");
		return -5;
	}
 
	/* set new path */
	strcpy (path, root);
	strcat (path, "/lib64/ld-2.20.so");
	args[0] = path;
 
	/* set real program path */
	strcpy (buf, root);
	strcat (buf, "/bin/");
	strcat (buf, argv[0]);
	strcat (buf, ".bin");
	args[1] = buf;
 
	/* copy arguments */
	for (i=1; i<argc; i++)
	      args[i+1] = argv[i];
	args[i+1] = NULL;
 
	/* run real program */
	return execv (path, args);;
}
gcc -O3 -o wrapper wrapper.c

Over!

mips64el toolchain for x86_64

mips64el toolchain 是用于在 x86_64 平台交叉编译 mips64el 目标程序的工具集,该工具集分为两种大版本:odd-spreg 和 no-odd-spreg,其中龙芯仅适用 no-odd-spreg 版本。系统库包含 mips64el o32, n32 和 n64 多种版本的库,分别有依赖于 Linux 2.6 内核和 Linux 3.4 内核的两种版本。另外还有支持 x86_64 交叉编译 Mozilla JS 引擎的支持包。

下载
Source: mips64el-toolchain-2.src.tar.xz
no-odd-spreg
toolchain: mips64el-toolchain-2.x64.tar.xz
system libaries (Linux 2.6): mips64el-toolchain-linux-2.6-2.x64.tar.xz
system libaries (Linux 3.4): mips64el-toolchain-linux-3.4-2.x64.tar.xz
system libaries (Linux 3.4 MozJS): mips64el-toolchain-linux-3.4-mozjs-2.x64.tar.xz
odd-spreg
toolchain: mips64el-toolchain-2.x64.tar.xz
system libaries (Linux 2.6): mips64el-toolchain-linux-2.6-2.x64.tar.xz
system libaries (Linux 3.4): mips64el-toolchain-linux-3.4-2.x64.tar.xz

安装

sudo tar --numeric-owner -xf xxxx -C /

配置
设置环境变量

export PATH=${PATH}:/opt/mips64el-toolchain/bin

切换系统库

sudo ln -s -f linux-2.6 /opt/mips64el-toolchain/platforms/current

编译

# MIPS32 o32
mips64el-unknown-linux-gnu-gcc -march=mips32r2 -mabi=32 -o test test.c
# MIPS64 n32
mips64el-unknown-linux-gnu-gcc -march=mips64r2 -mabi=n32 -o test test.c
# MIPS64 n64
mips64el-unknown-linux-gnu-gcc -march=mips64r2 -mabi=64 -o test test.c

Over!

GCC 预定义宏: Endian

#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
// little endian
#elif (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
// big endian
#elif (__BYTE_ORDER__ == __ORDER_PDP_ENDIAN__)
// pdp endian, The bytes in 16-bit words are laid out in
// a little-endian fashion, whereas the 16-bit subwords of
// a 32-bit quantity are laid out in big-endian fashion. 
#endif

Over!

Run command ‘su’ in PHP

升级 VPS 后,由于 Ubuntu 的 upstart 与 OpenVZ 的兼容问题,导致 sshd 服务不自动启动了,在尝试了 vePortal 的 console 与 file manager 及提交技术支持后都不能解决问题之后。

只能靠自己了,大概的思路是在 PHP 中进行 su 命令以执行 sshd 服务,因为 WordPress 还活着,并且可以在后台直接编辑主题相关的 PHP 脚本。只要把准备好的代码片断插入到 header.php 中,并在浏览器中访问一下主页即可。

相关的代码逻辑
1. 使用 PHP 的 proc_open 打开一个进程,重定向 stdin, stdout, stderr, 这里会执行一个 python 程序。
2. 在这个 python 程序中打开一个 pty,并运行一个 sh。
3. 利用步骤 1 中重定向的 stdin pipe 向 python 程序发送 su 命令, python 会将来自 stdin 的命令数据写到入 ptmx,而这时 sh 的 stdin, stdout 及 stderr 是重定向到与 python 打开的 ptmx 配对的 pts 上的。也就是说 su 命令最终会转给 sh 进程处理。
4. sh 进程自然的执行了 su 命令,这时 su 进程的 stdin, stdout, stderr 也会被重定向到那个 pts 上。
5. 在 sleep 一段时间后(主要是等 su 真的跑起来了),再写入密码,数据流过程与步骤 3、4 一致。

相关的代码片断

<?php
  $descriptorspec = array(
    0 => array("pipe", "r"),  // stdin
    1 => array("pipe", "w"),  // stdout
    2 => array("pipe", "w")   // stderr
  );
  $process = proc_open("python -c 'import pty; pty.spawn(\"/bin/sh\")'", $descriptorspec, $pipes);
  if (is_resource($process)) {
    fwrite($pipes[0], "su -c 'service ssh start' root\n");
    fflush($pipes[0]);
    sleep(3);
    fwrite($pipes[0], "PASSWORD\n");
    fflush($pipes[0]);
    fclose($pipes[0]);
    fclose($pipes[1]);
    fclose($pipes[2]);
    proc_close($process);
  } 
?>

Over!

MIPS N32 编译 Firefox 链接 libxu.so 时被 Kill

在 Arch Linux (MIPS N32) 系统中编译 Firefox,链接 libxul.so 过程中 ld 进程被 system 给 kill 了,多次尝试都如此。检查发现是 OOM 触发了 system 将进程 kill 了。增加 swap 后仍然无效,原因是 32-bit 的虚拟地址空间不足了。。。

在这种杯具的情况下,可以启动 ld 参数 –no-keep-memory 临时解决此问题。

--no-keep-memory
ld normally optimizes for speed over memory usage by caching the symbol tables
 of input files in memory. This option tells ld to instead optimize for memory
 usage, by rereading the symbol tables as necessary. This may be required if ld
 runs out of memory space while linking a large executable.

如果还是不行,那就要再加上 -S 或 –strip-debug

-S
--strip-debug
    Omit debugger symbol information (but not all symbols) from the output file.

Over!

Kernel 不支持 timerfd 使用 timeout

VPS 内核版本较低,低到还没实现 timerfd。由于是 OpenVZ 的方案,基本上是无法更新 Kernel 的。有一个必要应用正好要使用 HevLib 库的 HevEventSourceTimeout,此 source 是基于 timerfd 的,临时的解决方法是使用 HevEventSourceSignal 监听 SIGALRM 信号, 使用 alarm 或 ualaram 设置定时器:

#include <stdio.h>
#include <signal.h>
#include <hev-lib.h>
 
static bool
signal_handler (void *data)
{
        printf ("timeout signal\n");
 
        alarm (1);
 
        return true;
}
 
int
main (int argc, char *argv[])
{
        HevEventLoop *loop = NULL;
        HevEventSource *signal = NULL;
 
        loop = hev_event_loop_new ();
 
        signal = hev_event_source_signal_new (SIGALRM);
        hev_event_source_set_callback (signal, signal_handler, NULL, NULL);
        hev_event_loop_add_source (loop, signal);
        hev_event_source_unref (signal);
 
        alarm (1);
 
        hev_event_loop_run (loop);
 
        hev_event_loop_unref (loop);
 
        return 0;
}

Over!

Android 自动启动服务

最近在将 HevSocks5Client 移植到 Android 上了,在经过增加 signalfd 和 timerfd 相关的系统调用支持后,就可以直接使用 NDK 编译出 executable 了。直接的 native exectuable 在 Android 系统总还是不太方便用哦。还是做成一个 apk 吧,暂定只写一个 service 并开机自动启用,无 activity 的。

Java 中调用 native 程序我选择使用 JNI 方式,直接在 JNI_OnLoad 方法中调用 pthread_create 创建个线程跑原来的 main 就行啦。

...
#if defined(ANDROID)
#include <jni.h>
#include <pthread.h>
#endif
 
int
main (int argc, char *argv[])
{
    ...
}
 
#if defined(ANDROID)
static void *
thread_handler (void *data)
{
    main (0, NULL);
    return NULL;
}
 
jint
JNI_OnLoad (JavaVM *vm, void *reserved)
{
    pthread_t thread;
    pthread_create (&thread, NULL, thread_handler, NULL);
    return JNI_VERSION_1_4;
}
#endif

Android 服务
服务主要是加载 JNI 接口的 hev-socks5-client 库,使服务跑起来。

package hev.socks5;
 
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
 
public class MainService extends Service {
 
        static {
                System.loadLibrary("hev-socks5-client");
        }
 
        public IBinder onBind(Intent intent) {
                return null;
        }
}

BroadcastReceiver
ServiceReceiver 的功能就是监听系统上的 BOOT_COMPLETED 事件,用于实现自动启动服务。

package hev.socks5;
 
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
 
public class ServiceReceiver extends BroadcastReceiver {
 
        @Override
        public void onReceive(Context context, Intent intent) {
                if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
                        Intent i = new Intent(context, MainService.class);
                        context.startService(i);
                }
        }
}

AndroidManifest.xml
最后,要在 Manifest 中注册 Service 和 Receiver,增加上访问 Internet 和 Boot completed 的权限。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="hev.socks5"
          android:versionCode="1"
          android:versionName="1.0">
        <application android:label="@string/app_name" >
                <service android:name=".MainService">
                        <intent-filter>
                                <action android:name="hev.socks5.MainService" />
                        </intent-filter>
                </service>
                <receiver android:enabled="true" android:name=".ServiceReceiver">
                        <intent-filter>
                                <action android:name="android.intent.action.BOOT_COMPLETED" />
                        </intent-filter>
                </receiver>
        </application>
        <uses-permission android:name="android.permission.INTERNET" />
        <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
</manifest>

Tips
此方法仅在 Android 2.3.3 及之前版本有效,之后版本如果此应用在安装后从没运行过,Broadcast Receiver 将接收不到 boot completed 的 action,解决方法是使用命令手动启动一下 service。

am startservice hev.socks5/.MainService

Over!