Fix docker.yaml and sync

This commit is contained in:
xwings
2026-04-12 16:19:42 +08:00
9 changed files with 664 additions and 6 deletions
+4
View File
@@ -33,6 +33,7 @@ TARGETS = \
x8664_hello_cpp \
x8664_hello_cpp_static \
x8664_cloexec_test \
x8664_linux_onestraw \
patch_test.bin
.PHONY: all clean
@@ -124,7 +125,10 @@ libpatch_test.so: patch_test.so.h patch_test.so.c
$(CC) $(CPPFLAGS) -Wall -s -O0 -shared -fpic -o $@ patch_test.so.c
patch_test.bin: patch_test.bin.c libpatch_test.so
$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $^
x8664_onestraw_server: x8664_linux_onestraw.c
$(CC) $(CPPFLAGS) $(CFLAGS) -m64 -o $@ $<
$(OBJS):%.o:%.c
$(CC) $(CFLAGS) -c $< -o $@
+54
View File
@@ -0,0 +1,54 @@
#define MAX_EVENTS 5
#define READ_SIZE 10
#include <stdio.h> // for fprintf()
#include <unistd.h> // for close(), read()
#include <sys/epoll.h> // for epoll_create1(), epoll_ctl(), struct epoll_event
#include <string.h> // for strncmp
int main()
{
//setvbuf(stdin, NULL, _IONBF, 0);
int running = 1, event_count, i;
size_t bytes_read;
char read_buffer[READ_SIZE + 1];
struct epoll_event event, events[MAX_EVENTS];
int epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
fprintf(stderr, "Failed to create epoll file descriptor\n");
return 1;
}
event.events = EPOLLIN;
event.data.fd = 0;
if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, 0, &event))
{
fprintf(stderr, "Failed to add file descriptor to epoll\n");
close(epoll_fd);
return 1;
}
while (running) {
printf("\nPolling for input...\n");
event_count = epoll_wait(epoll_fd, events, MAX_EVENTS, 30000);
printf("%d ready events\n", event_count);
for (i = 0; i < event_count; i++) {
printf("Reading file descriptor '%d' -- ", events[i].data.fd);
bytes_read = read(events[i].data.fd, read_buffer, READ_SIZE);
printf("%zd bytes read.\n", bytes_read);
read_buffer[bytes_read] = '\0';
printf("Read '%s'\n", read_buffer);
if(!strncmp(read_buffer, "stop\n", 5))
running = 0;
}
}
if (close(epoll_fd)) {
fprintf(stderr, "Failed to close epoll file descriptor\n");
return 1;
}
return 0;
}
+199
View File
@@ -0,0 +1,199 @@
/*
* Attention:
* To keep things simple, do not handle socket/bind/listen/.../epoll_create/epoll_wait API error
*/
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#define DEFAULT_PORT 8000
#define MAX_CONN 16
#define MAX_EVENTS 32
#define BUF_SIZE 16
#define MAX_LINE 256
void server_run();
void client_run();
int main(int argc, char *argv[])
{
int opt;
char role = 's';
while ((opt = getopt(argc, argv, "cs")) != -1) {
switch (opt) {
case 'c':
role = 'c';
break;
case 's':
break;
default:
printf("usage: %s [-cs]\n", argv[0]);
exit(1);
}
}
if (role == 's') {
server_run();
} else {
client_run();
}
return 0;
}
/*
* register events of fd to epfd
*/
static void epoll_ctl_add(int epfd, int fd, uint32_t events)
{
struct epoll_event ev;
ev.events = events;
ev.data.fd = fd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) == -1) {
perror("epoll_ctl()\n");
exit(1);
}
}
static void set_sockaddr(struct sockaddr_in *addr)
{
bzero((char *)addr, sizeof(struct sockaddr_in));
addr->sin_family = AF_INET;
addr->sin_addr.s_addr = INADDR_ANY;
addr->sin_port = htons(DEFAULT_PORT);
}
static int setnonblocking(int sockfd)
{
if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL, 0) | O_NONBLOCK) ==
-1) {
return -1;
}
return 0;
}
/*
* epoll echo server
*/
void server_run()
{
int i;
int n;
int epfd;
int nfds;
int listen_sock;
int conn_sock;
int socklen;
char buf[BUF_SIZE];
struct sockaddr_in srv_addr;
struct sockaddr_in cli_addr;
struct epoll_event events[MAX_EVENTS];
listen_sock = socket(AF_INET, SOCK_STREAM, 0);
set_sockaddr(&srv_addr);
bind(listen_sock, (struct sockaddr *)&srv_addr, sizeof(srv_addr));
setnonblocking(listen_sock);
listen(listen_sock, MAX_CONN);
epfd = epoll_create(1);
epoll_ctl_add(epfd, listen_sock, EPOLLIN | EPOLLOUT | EPOLLET);
socklen = sizeof(cli_addr);
int a;
for (a = 0; a < 2; a++) {
nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
for (i = 0; i < nfds; i++) {
if (events[i].data.fd == listen_sock) {
/* handle new connection */
conn_sock =
accept(listen_sock,
(struct sockaddr *)&cli_addr,
&socklen);
inet_ntop(AF_INET, (char *)&(cli_addr.sin_addr),
buf, sizeof(cli_addr));
printf("[+] connected with %s:%d\n", buf,
ntohs(cli_addr.sin_port));
setnonblocking(conn_sock);
epoll_ctl_add(epfd, conn_sock,
EPOLLIN | EPOLLET | EPOLLRDHUP |
EPOLLHUP);
} else if (events[i].events & EPOLLIN) {
/* handle EPOLLIN event */
for (;;) {
bzero(buf, sizeof(buf));
n = read(events[i].data.fd, buf,
sizeof(buf));
if (n <= 0 /* || errno == EAGAIN */ ) {
break;
} else {
printf("[+] data: %s\n", buf);
write(events[i].data.fd, buf,
strlen(buf));
}
}
} else {
printf("[+] unexpected\n");
}
/* check if the connection is closing */
if (events[i].events & (EPOLLRDHUP | EPOLLHUP)) {
printf("[+] connection closed\n");
epoll_ctl(epfd, EPOLL_CTL_DEL,
events[i].data.fd, NULL);
close(events[i].data.fd);
continue;
}
}
}
}
/*
* test clinet
*/
void client_run()
{
int n;
int c;
int sockfd;
char buf[MAX_LINE];
struct sockaddr_in srv_addr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
set_sockaddr(&srv_addr);
if (connect(sockfd, (struct sockaddr *)&srv_addr, sizeof(srv_addr)) < 0) {
perror("connect()");
exit(1);
}
for (;;) {
printf("input: ");
fgets(buf, sizeof(buf), stdin);
c = strlen(buf) - 1;
buf[c] = '\0';
write(sockfd, buf, c + 1);
bzero(buf, sizeof(buf));
while (errno != EAGAIN
&& (n = read(sockfd, buf, sizeof(buf))) > 0) {
printf("echo: %s\n", buf);
bzero(buf, sizeof(buf));
c -= n;
if (c <= 0) {
break;
}
}
}
close(sockfd);
}
+26
View File
@@ -1064,3 +1064,29 @@ SHMAT = 21
SHMDT = 22
SHMGET = 23
SHMCTL = 24
# see: https://elixir.bootlin.com/linux/v5.19.17/source/include/uapi/linux/eventpoll.h
EPOLL_CTL_ADD = 1
EPOLL_CTL_DEL = 2
EPOLL_CTL_MOD = 3
EPOLLIN = 0x00000001
EPOLLPRI = 0x00000002
EPOLLOUT = 0x00000004
EPOLLERR = 0x00000008
EPOLLHUP = 0x00000010
EPOLLNVAL = 0x00000020
EPOLLRDNORM = 0x00000040
EPOLLRDBAND = 0x00000080
EPOLLWRNORM = 0x00000100
EPOLLWRBAND = 0x00000200
EPOLLMSG = 0x00000400
EPOLLRDHUP = 0x00002000
# TODO: should be aligned to emulated system's close-on-exec value
EPOLL_CLOEXEC = 0o2000000
EPOLLEXCLUSIVE = 0b1 << 28
EPOLLWAKEUP = 0b1 << 29
EPOLLONESHOT = 0b1 << 30
EPOLLET = 0b1 << 31
+1
View File
@@ -31,3 +31,4 @@ from .uio import *
from .unistd import *
from .utsname import *
from .wait import *
from .epoll import *
+294
View File
@@ -0,0 +1,294 @@
from __future__ import annotations
import ctypes
import select
from typing import TYPE_CHECKING, Dict, KeysView, NamedTuple
from qiling.os import struct
from qiling.os.posix.const import *
from qiling.os.filestruct import PersistentQlFile, ql_file
if TYPE_CHECKING:
from qiling import Qiling
from qiling.arch.arch import QlArch
from qiling.os.posix.posix import QlFileDes
class QlEpollEntry(NamedTuple):
"""A named tuple to represent an epoll entry.
This is used to store the events mask and the data for each entry in
the epoll instance.
"""
events: int
data: int
@struct.cache
def __make_epoll_event(arch: QlArch):
"""Create a structure to represent an epoll event.
"""
Struct = struct.get_packed_struct(arch.endian)
class epoll_event(Struct):
_fields_ = (
('events', ctypes.c_uint32),
('data', ctypes.c_uint64)
)
return epoll_event
class QlEpollObj:
def __init__(self, epoll_object: select.epoll):
self._epoll_object = epoll_object
# maps fd to eventmask
# keep track of which fds have what eventmasks,
# since this isn't directly supported in select.epoll
self._fds: Dict[int, QlEpollEntry] = {}
@property
def fds(self) -> KeysView[int]:
return self._fds.keys()
@property
def epoll_instance(self) -> select.epoll:
return self._epoll_object
def close(self) -> None:
self._epoll_object.close()
def __getitem__(self, fd: int) -> QlEpollEntry:
return self._fds[fd]
def __setitem__(self, fd: int, entry: QlEpollEntry) -> None:
# if fd is already being watched, modify its eventmask.
if fd in self:
self._epoll_object.modify(fd, entry.events)
# otherwise, register it with the epoll object
else:
self._epoll_object.register(fd, entry.events)
self._fds[fd] = entry
def __delitem__(self, fd: int) -> None:
"""Remove an fd from the epoll instance.
"""
self._fds.pop(fd)
self._epoll_object.unregister(fd)
def __contains__(self, fd: int) -> bool:
"""Test whether a specific fd is already being watched by this epoll instance.
"""
return fd in self.fds
def check_epoll_depth(ql_fd_list: QlFileDes) -> None:
"""Recursively check each epoll instance's 'watched' fds for an instance of
epoll being watched. If a chain of over 5 levels is detected, raise an exception
"""
def __visit_obj(obj: QlEpollObj, depth: int):
if depth >= 5:
raise RecursionError
for fd in obj.fds:
if isinstance(ql_fd_list[fd], QlEpollObj):
__visit_obj(obj, depth + 1)
for obj in ql_fd_list:
if isinstance(obj, QlEpollObj):
__visit_obj(obj, 1)
def ql_syscall_epoll_ctl(ql: Qiling, epfd: int, op: int, fd: int, event: int):
"""Modify an existing epoll.
"""
# not clear from man page, but to be safe don't support 'undefined' ops.
if op not in (EPOLL_CTL_ADD, EPOLL_CTL_DEL, EPOLL_CTL_MOD):
return -EINVAL
if epfd == fd:
return -EINVAL
if epfd not in range(NR_OPEN):
return -EBADF
epoll_parent_obj = ql.os.fd[epfd]
if not isinstance(epoll_parent_obj, QlEpollObj):
return -EINVAL
epoll_obj = epoll_parent_obj.epoll_instance
if epoll_obj is None:
return -EBADF
if epoll_obj.fileno() == fd:
return -ELOOP
# Qiling doesn't check process capabilities right now, so this case isn't explicitly handled yet
# EPOLLWAKEUP (since Linux 3.5)
# If EPOLLONESHOT and EPOLLET are clear and the process has the CAP_BLOCK_SUSPEND capability
fd_obj = ql.os.fd[fd]
if fd_obj is None:
return -EBADF
# TODO: not sure if qiling supports a way to determine if the target file descriptor is a
# directory. Here, check against PersistentQlFile is to ensure that polling stdin, stdout,
# stderr is supported
# The target file fd does not support epoll. This error can occur if fd refers to, for
# example, a regular file or a directory.
if isinstance(fd_obj, ql_file) and not isinstance(fd_obj, PersistentQlFile):
return -EPERM
# EPOLLEXCLUSIVE was specified in event and fd refers to an epoll instance
if isinstance(fd_obj, QlEpollObj) and (op & EPOLLEXCLUSIVE):
return -EINVAL
# Necessary to iterate over all possible qiling fds to determine if we have a chain of more
# than five epolls monitoring each other This may be removed in the future if the QlOsLinux
# class had a separate field for reserved for tracking epoll objects.
epolls_list = [fobj for fobj in ql.os.fd if isinstance(fobj, QlEpollObj)]
try:
check_epoll_depth(ql.os.fd)
except RecursionError:
return -ELOOP
if op == EPOLL_CTL_ADD:
# can't add an fd that's already being waited on
if fd in epoll_parent_obj:
return -EEXIST
if not event:
return -EINVAL
# dereference the event pointer to get structure fields
epoll_event_cls = __make_epoll_event(ql.arch)
epoll_event = epoll_event_cls.load_from(ql.mem, event)
# EPOLLEXCLUSIVE was specified in event and fd refers to an epoll instance
if isinstance(fd_obj, QlEpollObj) and (epoll_event.events & EPOLLEXCLUSIVE):
return -EINVAL
epoll_parent_obj[fd] = QlEpollEntry(
epoll_event.events,
epoll_event.data
)
elif op == EPOLL_CTL_DEL:
if fd not in epoll_parent_obj:
return -ENOENT
# remove from fds list and do so in the underlying epoll instance
del epoll_parent_obj[fd]
elif op == EPOLL_CTL_MOD:
if fd not in epoll_parent_obj:
return -ENOENT
if not event:
return -EINVAL
# dereference the event pointer to get structure fields
epoll_event_cls = __make_epoll_event(ql.arch)
epoll_event = epoll_event_cls.load_from(ql.mem, event)
# EPOLLEXCLUSIVE cannot be set on MOD operation, only on ADD
if epoll_event.events & EPOLLEXCLUSIVE:
return -EINVAL
epoll_parent_obj[fd] = QlEpollEntry(
epoll_event.events,
epoll_event.data
)
return 0
def ql_syscall_epoll_wait(ql: Qiling, epfd: int, epoll_events: int, maxevents: int, timeout: int):
"""Wait on an existing epoll for specific events.
"""
if maxevents <= 0:
return -EINVAL
# default value is 0xffffffff, but this fails when passing to epoll.poll()
if timeout == 0xFFFFFFFF:
timeout = None
if epfd not in range(NR_OPEN):
return -EBADF
epoll_parent_obj = ql.os.fd[epfd]
if not isinstance(epoll_parent_obj, QlEpollObj):
return -EINVAL
epoll_obj = epoll_parent_obj.epoll_instance
if epoll_obj is None:
return -EBADF
ready_fds = epoll_obj.poll(timeout, maxevents)
epoll_event_cls = __make_epoll_event(ql.arch)
# Each tuple in ready_fds consists of (file descriptor, eventmask) so we iterate
# through these to indicate which fds are ready and 'why'
#
# FIXME: emulated system fds are not the same as hosted system fds
for i, (fd, events) in enumerate(ready_fds):
entry = epoll_parent_obj[fd]
epoll_event = epoll_event_cls(events, entry.data)
offset = epoll_event_cls.sizeof() * i
ql.mem.write(epoll_events + offset, bytes(epoll_event))
# if no longer interested in this fd, remove from list
if events & EPOLLONESHOT:
del epoll_parent_obj[fd]
return len(ready_fds)
def __epoll_create(ql: Qiling, sizehint: int, flags: int) -> int:
# Use select.epoll for underlying implementation, just as select.poll is
# used for emulating poll()
ret = select.epoll(sizehint, flags)
fd = ret.fileno()
ql.os.fd[fd] = QlEpollObj(ret)
return fd
def ql_syscall_epoll_create1(ql: Qiling, flags: int):
if flags != select.EPOLL_CLOEXEC and flags != 0:
return -EINVAL
return __epoll_create(ql, -1, flags)
def ql_syscall_epoll_create(ql: Qiling, size: int):
if size < 0:
return -EINVAL
return __epoll_create(ql, size, 0)
+21
View File
@@ -222,6 +222,26 @@ class BaseStructEB(BaseStruct, ctypes.BigEndianStructure):
pass
@cache
def get_packed_struct(endian: QL_ENDIAN = QL_ENDIAN.EL) -> Type[BaseStruct]:
"""Provide a packed version of BaseStruct based on the emulated
architecture endianess.
Args:
archbits: required alignment in bits
"""
Struct = {
QL_ENDIAN.EL: BaseStructEL,
QL_ENDIAN.EB: BaseStructEB
}[endian]
class PackedStruct(Struct):
_pack_ = 1
return PackedStruct
@cache
def get_aligned_struct(archbits: int, endian: QL_ENDIAN = QL_ENDIAN.EL) -> Type[BaseStruct]:
"""Provide an aligned version of BaseStruct based on the emulated
@@ -229,6 +249,7 @@ def get_aligned_struct(archbits: int, endian: QL_ENDIAN = QL_ENDIAN.EL) -> Type[
Args:
archbits: required alignment in bits
endian: required endianness
"""
Struct = {
+64 -3
View File
@@ -9,8 +9,11 @@ import random
import os
import io
import re
import socket
import sys
import time
import threading
from ctypes import *
sys.path.append("..")
from typing import Any, Sequence
@@ -214,13 +217,13 @@ class ELFTest(unittest.TestCase):
ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_hello_static"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG)
ql.run()
del ql
def test_elf_linux_x86(self):
filename = 'test.qlog'
ql = Qiling(["../examples/rootfs/x86_linux/bin/x86_hello"], "../examples/rootfs/x86_linux", verbose=QL_VERBOSE.DEBUG, log_devices=[filename])
ql.run()
ql._log_file_fd.handlers[0].close() # prevent FD leak that causes downstream issues
os.remove(filename)
del ql
@@ -770,6 +773,64 @@ class ELFTest(unittest.TestCase):
del ql
@unittest.skip("stdin hijacking doesn't work as expected")
def test_elf_linux_x8664_epoll_simple(self):
# This tests a sample binary that (e)polls on stdin and echos back the output. Upon
# receiving 'stop', it will exit.
#
# epoll-0 tkaen from: https://github.com/maxasm/epoll-c/blob/main/main.c
rootfs = "../examples/rootfs/x8664_linux"
argv = r"../examples/rootfs/x8664_linux/bin/x8664_linux_epoll_0".split()
ql = Qiling(argv, rootfs, verbose=QL_VERBOSE.DEBUG)
ql.os.stdin = pipe.SimpleInStream(0)
ql.os.stdin.write(b'echo\n')
ql.os.stdin.write(b'stop\n') # signal to exit gracefully
ql.run()
self.assertIn(b'echo\n', ql.os.stdout.read())
del ql
def test_elf_linux_x8664_epoll_server(self):
# This tests a simple server that uses epoll to wait for data, then prints it out. It has
# been modified to exit after data has been received; instead of a typical server operation
# that reads requests indefinitely.
#
# It listens on port 8000, and a separate thread is spawned in order to test how the server
# handles a 'hello world' input. The server prints out whatever it receives, so the assert
# statement checks the input is present as expected.
#
# onestraw server taken from: https://github.com/onestraw/epoll-example
# Note: Without a hook for this syscall, this error fires:
# TypeError: stat: path should be string, bytes, os.PathLike or integer, not NoneType
def hook_newfstatat(ql: Qiling, dirfd: int, pathname: int, statbuf: int, flags: int):
return 0
def client():
# give time for the server to listen
time.sleep(3)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1", 8000))
s.send(b"hello world")
s.close()
rootfs = "../examples/rootfs/x8664_linux"
argv = r"../examples/rootfs/x8664_linux/bin/x8664_onestraw_server s".split() # s means 'server mode'
ql = Qiling(argv, rootfs, verbose=QL_VERBOSE.DEBUG)
ql.os.set_syscall("newfstatat", hook_newfstatat, QL_INTERCEPT.CALL)
ql.os.stdout = pipe.SimpleOutStream(1) # server prints data received to stdout
ql.filter = '^data:'
client_thread = threading.Thread(target=client, daemon=True)
client_thread.start()
ql.run()
self.assertIn(b'hello world', ql.os.stdout.read(200)) # 200 is arbitrary--"good enough" for this task
del ql
if __name__ == "__main__":
unittest.main()
+1 -3
View File
@@ -6,13 +6,12 @@
import http.client
import platform
import re
import socket
import sys
import os
import threading
import time
import unittest
import socket
from typing import List
sys.path.append("..")
@@ -636,6 +635,5 @@ class ELFTest(unittest.TestCase):
feedback = response.read()
self.assertEqual('httpd_test_successful', feedback.decode())
if __name__ == "__main__":
unittest.main()