Tracing a process using ptrace on Android ARM64 - android

I'm experiencing an infinite loop trying to trace simple hello world using ptrace() on Android ARM64 emulator that emulates AARCH64. I'm not sure why it is not stopping. I'm trying to trace a hello world program and get all executed instructions but it seems that this condition never returns false: while (WIFSTOPPED(wait_status))
int main(int argc, char** argv)
{
pid_t child_pid;
if(argc < 2)
{
fprintf(stderr, "Expected a program name as argument\n");
return -1;
}
child_pid = fork();
if (child_pid == 0)
run_target(argv[1]);
else if (child_pid > 0)
run_debugger(child_pid);
else
{
perror("fork");
return -1;
}
return 0;
}
void run_target(const char* programname)
{
printf("target started. will run '%s'\n", programname);
/* Allow tracing of this process */
if (ptrace(PTRACE_TRACEME, 0, 0, 0) < 0)
{
perror("ptrace");
return;
}
/* Replace this process's image with the given program */
execl(programname, programname, 0);
}
void run_debugger(pid_t child_pid)
{
int wait_status;
unsigned icounter = 0;
printf("debugger started\n");
/* Wait for child to stop on its first instruction */
wait(&wait_status);
while (WIFSTOPPED(wait_status))
{
icounter++;
struct user_pt_regs regs;
struct iovec io;
io.iov_base = &regs;
io.iov_len = sizeof(regs);
if (ptrace(PTRACE_GETREGSET, child_pid, (void*)NT_PRSTATUS, (void*)&io) == -1)
printf("BAD REGISTER REQUEST\n");
unsigned instr = ptrace(PTRACE_PEEKTEXT, child_pid, regs.pc, 0);
printf("icounter = %u. PCP = 0x%08x. instr = 0x%08x\n", icounter, regs.pc, instr);
/* Make the child execute another instruction */
if (ptrace(PTRACE_SINGLESTEP, child_pid, 0, 0) < 0)
{
perror("ptrace");
return;
}
/* Wait for child to stop on its next instruction */
wait(&wait_status);
}
printf("the child executed %u instructions\n", icounter);
}

Related

Calling ffmpeg.c's main twice causes app crash

Using FFmpeg 4.0.2 and call its ffmpeg.c's main function twice causes Android app crash (using FFmpeg shared libs and JNI)
A/libc: Fatal signal 11 (SIGSEGV), code 1, fault addr 0x0 in tid 20153
Though it works ok for FFmpeg 3.2.5
FFmpeg 4.0.2 main
int main(int argc, char **argv) {
int i, ret;
int64_t ti;
init_dynload();
register_exit(ffmpeg_cleanup);
setvbuf(stderr,NULL,_IONBF,0); /* win32 runtime needs this */
av_log_set_flags(AV_LOG_SKIP_REPEATED);
parse_loglevel(argc, argv, options);
if(argc>1 && !strcmp(argv[1], "-d")){
run_as_daemon=1;
av_log_set_callback(log_callback_null);
argc--;
argv++;
}
#if CONFIG_AVDEVICE
avdevice_register_all();
#endif
avformat_network_init();
show_banner(argc, argv, options);
/* parse options and open all input/output files */
ret = ffmpeg_parse_options(argc, argv);
if (ret < 0)
exit_program(1);
if (nb_output_files <= 0 && nb_input_files == 0) {
show_usage();
av_log(NULL, AV_LOG_WARNING, "Use -h to get full help or, even better, run 'man %s'\n", program_name);
exit_program(1);
}
/* file converter / grab */
if (nb_output_files <= 0) {
av_log(NULL, AV_LOG_FATAL, "At least one output file must be specified\n");
exit_program(1);
}
// if (nb_input_files == 0) {
// av_log(NULL, AV_LOG_FATAL, "At least one input file must be specified\n");
// exit_program(1);
// }
for (i = 0; i < nb_output_files; i++) {
if (strcmp(output_files[i]->ctx->oformat->name, "rtp"))
want_sdp = 0;
}
current_time = ti = getutime();
if (transcode() < 0)
exit_program(1);
ti = getutime() - ti;
if (do_benchmark) {
av_log(NULL, AV_LOG_INFO, "bench: utime=%0.3fs\n", ti / 1000000.0);
}
av_log(NULL, AV_LOG_DEBUG, "%"PRIu64" frames successfully decoded, %"PRIu64" decoding errors\n",
decode_error_stat[0], decode_error_stat[1]);
if ((decode_error_stat[0] + decode_error_stat[1]) * max_error_rate < decode_error_stat[1])
exit_program(69);
ffmpeg_cleanup(received_nb_signals ? 255 : main_return_code);
return main_return_code;
}
FFmpeg 3.2.5 main
int main(int argc, char **argv) {
av_log(NULL, AV_LOG_WARNING, " Command start");
int i, ret;
int64_t ti;
init_dynload();
register_exit(ffmpeg_cleanup);
setvbuf(stderr, NULL, _IONBF, 0); /* win32 runtime needs this */
av_log_set_flags(AV_LOG_SKIP_REPEATED);
parse_loglevel(argc, argv, options);
if (argc > 1 && !strcmp(argv[1], "-d")) {
run_as_daemon = 1;
av_log_set_callback(log_callback_null);
argc--;
argv++;
}
avcodec_register_all();
#if CONFIG_AVDEVICE
avdevice_register_all();
#endif
avfilter_register_all();
av_register_all();
avformat_network_init();
av_log(NULL, AV_LOG_WARNING, " Register to complete the codec");
show_banner(argc, argv, options);
/* parse options and open all input/output files */
ret = ffmpeg_parse_options(argc, argv);
if (ret < 0)
exit_program(1);
if (nb_output_files <= 0 && nb_input_files == 0) {
show_usage();
av_log(NULL, AV_LOG_WARNING, "Use -h to get full help or, even better, run 'man %s'\n",
program_name);
exit_program(1);
}
/* file converter / grab */
if (nb_output_files <= 0) {
av_log(NULL, AV_LOG_FATAL, "At least one output file must be specified\n");
exit_program(1);
}
// if (nb_input_files == 0) {
// av_log(NULL, AV_LOG_FATAL, "At least one input file must be specified\n");
// exit_program(1);
// }
for (i = 0; i < nb_output_files; i++) {
if (strcmp(output_files[i]->ctx->oformat->name, "rtp"))
want_sdp = 0;
}
current_time = ti = getutime();
if (transcode() < 0)
exit_program(1);
ti = getutime() - ti;
if (do_benchmark) {
av_log(NULL, AV_LOG_INFO, "bench: utime=%0.3fs\n", ti / 1000000.0);
}
av_log(NULL, AV_LOG_DEBUG, "%"PRIu64" frames successfully decoded, %"PRIu64" decoding errors\n",
decode_error_stat[0], decode_error_stat[1]);
if ((decode_error_stat[0] + decode_error_stat[1]) * max_error_rate < decode_error_stat[1])
exit_program(69);
exit_program(received_nb_signals ? 255 : main_return_code);
nb_filtergraphs = 0;
nb_input_streams = 0;
nb_input_files = 0;
progress_avio = NULL;
input_streams = NULL;
nb_input_streams = 0;
input_files = NULL;
nb_input_files = 0;
output_streams = NULL;
nb_output_streams = 0;
output_files = NULL;
nb_output_files = 0;
return main_return_code;
}
So what could be issue? It seems FFmpeg 4.0.2 doesn't release something (resources or its static variables to initial values after the first command)
Adding next lines from FFmpeg 3.2.5 to FFmpeg 4.0.2 to the end of main function solved the problem (I downloaded FFmpeg 3.2.5 as someone's Android project so that user added those lines)
nb_filtergraphs = 0;
nb_input_streams = 0;
nb_input_files = 0;
progress_avio = NULL;
input_streams = NULL;
nb_input_streams = 0;
input_files = NULL;
nb_input_files = 0;
output_streams = NULL;
nb_output_streams = 0;
output_files = NULL;
nb_output_files = 0;

why getpidcon() error on AOSP 7.1.2?

I am working on making a customized bsp based on AOSP Nougat latest source.
Android service process ask service manager to find or add the service.
And service manager try to check mac permissions by calling svc_can_register() or svc_can_find() which calls check_mac_perms() which calls getpidcon().
Let's see svc_can_find()
static int svc_can_find(const uint16_t *name, size_t name_len, pid_t spid, uid_t uid)
{
const char *perm = "find";
return check_mac_perms_from_lookup(spid, uid, perm, str8(name, name_len)) ? 1 : 0;
}
check_mac_perms_from_lookup() is like this:
static bool check_mac_perms_from_lookup(pid_t spid, uid_t uid, const char *perm, const char *name)
{
bool allowed;
char *tctx = NULL;
if (selinux_enabled <= 0) {
return true;
}
if (!sehandle) {
ALOGE("SELinux: Failed to find sehandle. Aborting service_manager.\n");
abort();
}
if (selabel_lookup(sehandle, &tctx, name, 0) != 0) {
ALOGE("SELinux: No match for %s in service_contexts.\n", name);
return false;
}
allowed = check_mac_perms(spid, uid, tctx, perm, name);
freecon(tctx);
return allowed;
}
It calls check_mac_perms(). check_mac_perms() like this:
static bool check_mac_perms(pid_t spid, uid_t uid, const char *tctx, const char *perm, const char *name)
{
char *sctx = NULL;
const char *class = "service_manager";
bool allowed;
struct audit_data ad;
if (getpidcon(spid, &sctx) < 0) {
ALOGE("SELinux: getpidcon(pid=%d) failed to retrieve pid context.\n", spid);
return false;
}
ad.pid = spid;
ad.uid = uid;
ad.name = name;
int result = selinux_check_access(sctx, tctx, class, perm, (void *) &ad);
allowed = (result == 0);
freecon(sctx);
return allowed;
}
It calls getpidcon(). getpidcon() is defined in
external/selinux/libselinux/src/procattr.c
getpidcon() is defined like this:
#define getpidattr_def(fn, attr) \
int get##fn(pid_t pid, char **c) \
{ \
if (pid <= 0) { \
errno = EINVAL; \
return -1; \
} else { \
return getprocattrcon(c, pid, #attr); \
} \
}
...
...
getpidattr_def(pidcon, current)
"getpidattr_def(pidcon, current)" is expanded to getpidcon() function
definition and it calls getprocatrcon()
getprocattrcon() is like this:
static int getprocattrcon(char ** context,
pid_t pid, const char *attr)
{
char *buf;
size_t size;
int fd;
ssize_t ret;
int errno_hold;
fd = openattr(pid, attr, O_RDONLY);
if (fd < 0)
return -1;
size = selinux_page_size;
buf = malloc(size);
if (!buf) {
ret = -1;
goto out;
}
memset(buf, 0, size);
do {
ret = read(fd, buf, size - 1);
} while (ret < 0 && errno == EINTR);
if (ret < 0)
goto out2;
if (ret == 0) {
*context = NULL;
goto out2;
}
*context = strdup(buf);
if (!(*context)) {
ret = -1;
goto out2;
}
ret = 0;
out2:
free(buf);
out:
errno_hold = errno;
close(fd);
errno = errno_hold;
return ret;
}
Pretty simple huh? Just opening some files and reading the contents
and return it by function argument.
It fails at openattr(). I've confirmed this by inserting some log function in
openattr(). openattr() is also simple function.
static int openattr(pid_t pid, const char *attr, int flags)
{
int fd, rc;
char *path;
pid_t tid;
if (pid > 0) {
rc = asprintf(&path, "/proc/%d/attr/%s", pid, attr);
} else if (pid == 0) {
rc = asprintf(&path, "/proc/thread-self/attr/%s", attr);
if (rc < 0)
return -1;
fd = open(path, flags | O_CLOEXEC);
if (fd >= 0 || errno != ENOENT)
goto out;
free(path);
tid = gettid();
rc = asprintf(&path, "/proc/self/task/%d/attr/%s", tid, attr);
} else {
errno = EINVAL;
return -1;
}
if (rc < 0)
return -1;
fd = open(path, flags | O_CLOEXEC);
out:
free(path);
return fd;
}
The fail point is "fd = open(path, flags | O_CLOEXEC);"
Even if the file exists, almost always opening fails. I don't understand ths and want to know what caused the problem. I've confirmed the failure
by inserting some log printing codes, checking android log(adb logcat) and reading the file from android shell(adb shell), e.g. 'cat /proc/412/attr/current'. Reading by 'cat ...' succeeded but log shows the opening the
file fails. The odd thing is if 'pid' is 0, it succeeds.
If opening fails, services can't be launched so the system don't
boot properly. If I ignore the fails and return success from getpidcon()
the system boots properly but this is not the right thing to do obviously.
I'm testing the bsp as selinux permissive mode.
Can anyone have a experience like me? If anyone, please share the
experience and the solution of the problem.
Thank you.
Sangyong Lee.

ASensorEventQueue_hasEvents returns -1 and ALooper_pollAll returns -4 on basic usage

I am trying to write an android app which uses the Java SDK for the GUI, and the NDK to draw things and read the Linear Acceleration sensor (based of the accelerometer but does not include gravity). Basically, I want to know when the device is being shaken and my app is active.
I am using ASensorEventQueue_hasEvents and ALooper_pollAll in a fairly basic manner, I think, but they return -1 and -4 respectively. These are errors, but I have no clue what they mean or what I could be doing wrong. Any help would be welcome, thanks! Here are some code snips:
To start listening for data from the linear acceleration sensor, I did:
//sensorManager = ASensorManager_getInstance();
sensorManager = ASensorManager_getInstanceForPackage(mypackage);
sensor = ASensorManager_getDefaultSensor(sensorManager, ASENSOR_TYPE_LINEAR_ACCELERATION);
if (sensor == nullptr) {
return env->NewStringUTF("Linear accelerometer is not present");
}
looper = ALooper_prepare(0);
if (looper == nullptr) {
return env->NewStringUTF("Could not get looper.");
}
eventQueue = ASensorManager_createEventQueue(sensorManager, looper, EVENT_TYPE_LINEAR_ACCELEROMETER, nullptr, nullptr);
if (eventQueue == nullptr) {
return env->NewStringUTF("Could not create event queue for sensor");
}
//ASensorEventQueue_disableSensor(eventQueue, sensor);
int minDelay = ASensor_getMinDelay(sensor);
minDelay = std::max(minDelay, MAX_EVENT_REPORT_TIME);
int rc = ASensorEventQueue_registerSensor(eventQueue, sensor, minDelay, minDelay);
rc = ASensorEventQueue_setEventRate(eventQueue, sensor, minDelay);
return env->NewStringUTF("");
And my drawing loop looks like:
int rc = ASensorEventQueue_enableSensor(eventQueue, sensor);
std::vector<std::string> results;
while (!stopDrawing) {
int event = 0;
void *data = nullptr;
int rc;
rc = ASensorEventQueue_hasEvents(eventQueue); // testing
rc = ALooper_pollAll(wait_timeout, nullptr, &event, &data);
if (rc == ALOOPER_POLL_TIMEOUT) {
waiting += wait_timeout;
if (waiting > EVENT_LOOP_TIMEOUT) {
// If no data has been received, we have been waiting for a while, so assume
// the shaking has stopped
ASensorEventQueue_disableSensor(eventQueue, sensor);
}
} else if (rc == ALOOPER_POLL_ERROR) {
return env->NewStringUTF("A looper error occurred");
} else if (rc >= 0 && event == EVENT_TYPE_LINEAR_ACCELEROMETER) {
std::vector<ASensorEvent> events;
events.resize(100);
while (ASensorEventQueue_hasEvents(eventQueue) == 1) {
ssize_t nbrEvents = ASensorEventQueue_getEvents(eventQueue, events.data(), events.size());
if (nbrEvents < 0) {
// an error has occurred
return env->NewStringUTF("");
}
float x = 0;
float y = 0;
float z = 0;
for (int i = 0; i < nbrEvents; i++) {
x += events[i].acceleration.x;
y += events[i].acceleration.y;
z += events[i].acceleration.z;
}
graphics.updateAcceleration(x, y, z);
}
}
graphics.updateUniformBuffer();
graphics.drawFrame();
}
Note: the call to ASensorEventQueue_hasEvents right before ALooper_pollAll was just so that I could see what it was returning since the ALooper_pollAll call always returned an error. When running it in the debugger, I see that hasEvents is returning -1 and pollAll is returning -4. This is happening in SDK versions 24 and 26 (I haven't tried 27 and vulkan is not supported below 24). Any ideas why?
I had the precise symptoms described here, and for me it turned out that I was preparing the looper on a different thread than I was polling on.

Using glutIdleFunc to get out of glutMainLoop?

Im writing a programme that opens and openGL windows with an image and connects to my android device where the user uses the device as a sort of trackpad to pan and zoom in and out. All is working fine however the programme gets stuck in the glutMainLoop and will not proceed with accepting data from the device. Apparently glutIdleFunc is the solution to my problem however i cant see how to implement this in my code without getting a memory error? Could someone show me how to put the function into my code so it runs the connection code as well as the opengl stuff?
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <strings.h>
#include <vrpn_Shared.h>
#include <vrpn_Analog.h>
#include <vector>
#include <GL/freeglut.h>
#include <imageviewer.h>
using namespace std;
int done = 0;
int accepted = 0; // Signals that the program should exit
unsigned tracker_stride = 1; // Every nth report will be printed
//-------------------------------------
// This section contains the data structure that holds information on
// the devices that are created. For each named device, a remote of each
// type analog is created.
class device_info {
public:
char *name;
vrpn_Analog_Remote *ana;
};
const unsigned MAX_DEVICES = 2;
//-------------------------------------
// This section contains the data structure that is used to determine how
// often to print a report for each sensor of each tracker. Each element
// contains a counter that is used by the callback routine to keep track
// of how many it has skipped. There is an element for each possible sensor.
// A new array of elements is created for each new tracker object, and a
// pointer to it is passed as the userdata pointer to the callback handlers.
class t_user_callback {
public:
char t_name[vrpn_MAX_TEXT_LEN];
vector<unsigned> t_counts ;
};
//Callback handlers
void VRPN_CALLBACK handle_analog (void *userdata, const vrpn_ANALOGCB a)
{
int i;
const char *name = (const char *)userdata;
printf("Input from %s:\n \n %5.0f", name, a.channel[0]);
for (i = 1; i < a.num_channel; i++) {
printf(" %5.0f \n", a.channel[1]);
}
printf(" \n");
}
int main (int argc, char * argv [])
{
int print_for_tracker = 1; // Print tracker reports?
int print_for_button = 1; // Print button reports?
int print_for_analog = 1; // Print analog reports?
int print_for_dial = 1; // Print dial reports?
int print_for_text = 1; // Print warning/error messages?
device_info device_list[MAX_DEVICES];
unsigned num_devices = 0;
int i;
// Parse arguments, creating objects
for (i = 1; i < argc; i++) {
if (!strcmp(argv[i], "-notracker")) {
print_for_tracker = 0;
} else if (!strcmp(argv[i], "-nobutton")) {
print_for_button = 0;
} else if (!strcmp(argv[i], "-noanalog")) {
print_for_analog = 0;
} else if (!strcmp(argv[i], "-nodial")) {
print_for_dial = 0;
} else if (!strcmp(argv[i], "-notext")) {
print_for_text = 0;
} else if (!strcmp(argv[i], "-trackerstride")) {
if (tracker_stride <= 0) {
fprintf(stderr, "-trackerstride argument must be 1 or greater\n");
return -1;
}
} else { // Create a device and connect to it.
device_info *dev;
// Name the device and open it as everything
dev = &device_list[num_devices];
dev->name = argv[i];
dev->ana = new vrpn_Analog_Remote(dev->name);
if (print_for_analog) {
printf(" Analog");
dev->ana->register_change_handler(dev->name, handle_analog);
}
printf(".\n");
num_devices++;
}
}
// main interactive loop
printf("Press ^C to exit.\n");
while ( ! done ) {
unsigned i;
// Let all the devices do their things
for (i = 0; i < num_devices; i++) {
device_list[i].ana->mainloop();
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
glutInitWindowSize(400,300);
glutInitWindowPosition(200,100);
glutCreateWindow("ImageViewer");
init();
glutDisplayFunc(display);
glutMotionFunc(drag);
glutMouseFunc(mouse);
// glutIdleFunc(IdleFunc);
glutMainLoop();
}
}
return 0;
}
glut is fine if it can manage all of the input devices, and everything is event-driven from the inputs that it manages. Once you have unmanaged input devices or non-event-based processing, you probably want to use something other than glut. Your other alternative is to fork and run your asynchronous stuff in a separate process (or thread).

CreateProcess for android adb start-server?

When I use CreateProcess to create process adb.exe, It will Block in ReadFile.
void KillAdbProcess()
{
DWORD aProcesses[1024], cbNeeded, cProcesses;
unsigned int i;
if ( !EnumProcesses( aProcesses, sizeof(aProcesses), &cbNeeded ) )
return;
cProcesses = cbNeeded / sizeof(DWORD);
for ( i = 0; i < cProcesses; i++ )
if( aProcesses[i] != 0 ){
bool shouldKill =false;
wchar_t szProcessName[MAX_PATH] = L"<unknown>";
//Get a handle to the process.
HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION |
PROCESS_VM_READ | PROCESS_TERMINATE,
FALSE, aProcesses[i] );
if (NULL != hProcess )
{
HMODULE hMod;
DWORD cbNeeded;
if ( EnumProcessModules( hProcess, &hMod, sizeof(hMod),
&cbNeeded) )
{
GetModuleFileNameExW( hProcess, hMod, szProcessName,
sizeof(szProcessName)/sizeof(TCHAR));
int len = wcslen(szProcessName);
if(!wcscmp(L"\\adb.exe",szProcessName+len-8)){
shouldKill = true;
}
}
}
if(shouldKill) TerminateProcess(hProcess,0);
CloseHandle( hProcess );
}
}
int testadb(){
KillAdbProcess();
char buff[4096] = {0};
int len = sizeof(buff);
DWORD exitCode = 0;
SECURITY_ATTRIBUTES sa;
ZeroMemory(&sa, sizeof(sa));
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
sa.nLength = sizeof(sa);
HANDLE hOutputReadTmp,hOutputRead,hOutputWrite;
// Create the child output pipe.
if (!CreatePipe(&hOutputReadTmp,&hOutputWrite,&sa,0))
return false;
// Create new output read handle and the input write handles. Set
// the Properties to FALSE. Otherwise, the child inherits the
// properties and, as a result, non-closeable handles to the pipes
// are created.
if (!DuplicateHandle(GetCurrentProcess(),hOutputReadTmp,
GetCurrentProcess(),
&hOutputRead, // Address of new handle.
0,FALSE, // Make it uninheritable.
DUPLICATE_SAME_ACCESS))
return false;
// Close inheritable copies of the handles you do not want to be
// inherited.
if (!CloseHandle(hOutputReadTmp)) return false;
PROCESS_INFORMATION pi;
ZeroMemory(&pi, sizeof(pi));
STARTUPINFOW si;
GetStartupInfoW(&si);
si.cb = sizeof(STARTUPINFO);
si.dwFlags = STARTF_USESTDHANDLES;
si.wShowWindow = SW_HIDE;
si.hStdInput = NULL;
if(buff) {
si.hStdOutput = hOutputWrite;
si.hStdError = hOutputWrite;
} else {
si.hStdOutput = NULL;
si.hStdError = NULL;
}
wchar_t cmdBuf[512] = L"adb.exe start-server";
if( !::CreateProcessW(NULL, cmdBuf, NULL, NULL, TRUE, DETACHED_PROCESS, NULL, NULL, &si, &pi) )
{
exitCode = -1;
goto exit;
}
::CloseHandle(hOutputWrite);
hOutputWrite = NULL;
len--; //keep it for string end char.
DWORD dwBytes = 0;
DWORD dwHasRead = 0;
while(::ReadFile(hOutputRead, buff+dwHasRead, len-dwHasRead, &dwBytes, NULL))
{
printf("read byte=%d\n",dwBytes);
if(0 == dwBytes) break;
dwHasRead += dwBytes;
//GetExitCodeProcess(pi.hProcess, &exitCode);
//if(STILL_ACTIVE != exitCode) break;
if(dwHasRead >= len) break;
}
buff[dwHasRead] = 0;
::GetExitCodeProcess(pi.hProcess, &exitCode);
exit:
if(hOutputRead) ::CloseHandle(hOutputRead);
if(hOutputWrite) ::CloseHandle(hOutputWrite);
::CloseHandle(pi.hProcess);
::CloseHandle(pi.hThread);
return 0;
}
If I change code to
while(::ReadFile(hOutputRead, buff+dwHasRead, len-dwHasRead, &dwBytes, NULL))
{
printf("read byte=%d\n",dwBytes);
if(0 == dwBytes) break;
dwHasRead += dwBytes;
GetExitCodeProcess(pi.hProcess, &exitCode);
if(STILL_ACTIVE != exitCode) break;
if(dwHasRead >= len) break;
}
it works, but when I delete printf code, it will block again.
while(::ReadFile(hOutputRead, buff+dwHasRead, len-dwHasRead, &dwBytes, NULL))
{
if(0 == dwBytes) break;
dwHasRead += dwBytes;
GetExitCodeProcess(pi.hProcess, &exitCode);
if(STILL_ACTIVE != exitCode) break;
if(dwHasRead >= len) break;
}
In the code of adb.exe, I see some code like belows:
#if ADB_HOST
int launch_server()
{
#ifdef HAVE_WIN32_PROC
/* we need to start the server in the background */
/* we create a PIPE that will be used to wait for the server's "OK" */
/* message since the pipe handles must be inheritable, we use a */
/* security attribute */
HANDLE pipe_read, pipe_write;
SECURITY_ATTRIBUTES sa;
STARTUPINFO startup;
PROCESS_INFORMATION pinfo;
char program_path[ MAX_PATH ];
int ret;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
/* create pipe, and ensure its read handle isn't inheritable */
ret = CreatePipe( &pipe_read, &pipe_write, &sa, 0 );
if (!ret) {
fprintf(stderr, "CreatePipe() failure, error %ld\n", GetLastError() );
return -1;
}
SetHandleInformation( pipe_read, HANDLE_FLAG_INHERIT, 0 );
ZeroMemory( &startup, sizeof(startup) );
startup.cb = sizeof(startup);
startup.hStdInput = GetStdHandle( STD_INPUT_HANDLE );
startup.hStdOutput = pipe_write;
startup.hStdError = GetStdHandle( STD_ERROR_HANDLE );
startup.dwFlags = STARTF_USESTDHANDLES;
ZeroMemory( &pinfo, sizeof(pinfo) );
/* get path of current program */
GetModuleFileName( NULL, program_path, sizeof(program_path) );
ret = CreateProcess(
program_path, /* program path */
"adb fork-server server",
/* the fork-server argument will set the
debug = 2 in the child */
NULL, /* process handle is not inheritable */
NULL, /* thread handle is not inheritable */
TRUE, /* yes, inherit some handles */
DETACHED_PROCESS, /* the new process doesn't have a console */
NULL, /* use parent's environment block */
NULL, /* use parent's starting directory */
&startup, /* startup info, i.e. std handles */
&pinfo );
CloseHandle( pipe_write );
if (!ret) {
fprintf(stderr, "CreateProcess failure, error %ld\n", GetLastError() );
CloseHandle( pipe_read );
return -1;
}
CloseHandle( pinfo.hProcess );
CloseHandle( pinfo.hThread );
/* wait for the "OK\n" message */
{
char temp[3];
DWORD count;
ret = ReadFile( pipe_read, temp, 3, &count, NULL );
CloseHandle( pipe_read );
if ( !ret ) {
fprintf(stderr, "could not read ok from ADB Server, error = %ld\n", GetLastError() );
return -1;
}
if (count != 3 || temp[0] != 'O' || temp[1] != 'K' || temp[2] != '\n') {
fprintf(stderr, "ADB server didn't ACK\n" );
return -1;
}
}
#elif defined(HAVE_FORKEXEC)
char path[PATH_MAX];
int fd[2];
// set up a pipe so the child can tell us when it is ready.
// fd[0] will be parent's end, and fd[1] will get mapped to stderr in the child.
if (pipe(fd)) {
fprintf(stderr, "pipe failed in launch_server, errno: %d\n", errno);
return -1;
}
get_my_path(path);
pid_t pid = fork();
if(pid < 0) return -1;
if (pid == 0) {
// child side of the fork
// redirect stderr to the pipe
// we use stderr instead of stdout due to stdout's buffering behavior.
adb_close(fd[0]);
dup2(fd[1], STDERR_FILENO);
adb_close(fd[1]);
// child process
int result = execl(path, "adb", "fork-server", "server", NULL);
// this should not return
fprintf(stderr, "OOPS! execl returned %d, errno: %d\n", result, errno);
} else {
// parent side of the fork
char temp[3];
temp[0] = 'A'; temp[1] = 'B'; temp[2] = 'C';
// wait for the "OK\n" message
adb_close(fd[1]);
int ret = adb_read(fd[0], temp, 3);
adb_close(fd[0]);
if (ret < 0) {
fprintf(stderr, "could not read ok from ADB Server, errno = %d\n", errno);
return -1;
}
if (ret != 3 || temp[0] != 'O' || temp[1] != 'K' || temp[2] != '\n') {
fprintf(stderr, "ADB server didn't ACK\n" );
return -1;
}
setsid();
}
#else
#error "cannot implement background server start on this platform"
#endif
return 0;
}
#endif
I think the child process of adb.exe inherit the handle of adb.exe, if the child process of adb.exe doesn't exit, ReadFile will block for ever. But when I exec "adb.exe start-server" in command, all is Ok. So how does windows command call CreateProcess and ReadFile?
I have found the answer: Redirecting an arbitrary Console's Input/Output - CodeProject.
The technique of redirecting the input/output of a console process is very sample: The CreateProcess() API through the STARTUPINFO structure enables us to redirect the standard handles of a child console based process. So we can set these handles to either a pipe handle, file handle, or any handle that we can read and write. The detail of this technique has been described clearly in MSDN: HOWTO: Spawn Console Processes with Redirected Standard Handles.
However, MSDN's sample code has two big problem. First, it assumes the child process will send output at first, then wait for input, then flush the output buffer and exit. If the child process doesn't behave like that, the parent process will be hung up. The reason of this is the ReadFile() function remains blocked untill the child process sends some output, or exits.
Second, It has problem to redirect a 16-bit console (including console based MS-DOS applications.) On Windows 9x, ReadFile remains blocked even after the child process has terminated; On Windows NT/XP, ReadFile always returns FALSE with error code set to ERROR_BROKEN_PIPE if the child process is a DOS application.
Solving the block problem of ReadFile
To prevent the parent process from being blocked by ReadFile, we can simply pass a file handle as stdout to the child process, then monitor this file. A more simple way is to call PeekNamedPipe() function before calling ReadFile(). The PeekNamedPipe function checks information about data in the pipe, then returns immediately. If there's no data available in the pipe, don't call ReadFile.
By calling PeekNamedPipe before ReadFile, we also solve the block problem of redirecting a 16-bit console on Windows 9x.

Categories

Resources