Hi there,

Disclaimer: total and utter assembler noob

I've written a small program to read the contents of the sys_getdents function and parse its output, printing the output to stdout using a call to C's printf function. However, although I can see the (expected) output on-screen, I can't redirect or pipe the output using '>' or '|'.

Can anyone suggest why this is happening? The program's dynamically linked and was compiled with as and linked with ld.

Cheers

Michael
Posted on 2011-04-08 10:19:26 by michaelg
I don't think this accounts for what you're seeing, but I'll mention it anyway. I've noticed an "oddity" to printf: it doesn't actually print anything until the buffer is flushed. Printing a linefeed ("\n" or 0x0A) will flush the buffer, as will doing buffered input, as will exiting the program. However if I, for example, say "Please tell me your name " using printf - no linefeed, I want the input on the same line - and then get the name using sys_read, nothing is printed. It waits for input with no "prompt". Then, at the end, prints "Please tell me your name "/ "Hello Frank". I guess that's why the manual advises us not to mix "buffered I/O" with "low level I/O". I would ASSume that redirected output would be "printed" at the end, too... but maybe not... I don't think that's "it", but it's the only thing I can think of. If you're not ending your format string with a linefeed, try it.

If this is 32-bit code, I'll test it for ya if you post it (old Slackware distro). Can't help with 64-bit.

Best,
Frank

Posted on 2011-04-08 14:03:08 by fbkotler
@Michael:

Huh!?

Seeing some code here would probably help, but I'm going to take a wild shot in the dark and suggest the following.

./sgdapp &> file.log


Or if you are trying to pass it to another command, try something like this.

./sgdapp 2>&1 | awk '{print $1,$3;}'


That command takes the first and third column of output from ./sgdapp and displays it. If either of these work, then your output is getting lost on it's path to stdout and ending up in stderr. In that case check your file handles. If it doesn't work, I would probably agree with fbkotler, there might not be a terminating newline at the end of your output, either insert one manually or call fflush() to force the I/O buffer to be drawn to the screen.

@Frank:

I've noticed an "oddity" to printf: it doesn't actually print anything until the buffer is flushed. Printing a linefeed ("\n" or 0x0A) will flush the buffer, as will doing buffered input, as will exiting the program. However if I, for example, say "Please tell me your name " using printf - no linefeed, I want the input on the same line - and then get the name using sys_read, nothing is printed. It waits for input with no "prompt". Then, at the end, prints "Please tell me your name "/ "Hello Frank". I guess that's why the manual advises us not to mix "buffered I/O" with "low level I/O".


This behavior is completely expected and yes it's why they warn about using buffered vs low level I/O together. However, there is a solution. fflush() can be called to force the buffered routine to flush the contents of the file descriptor without needing a linefeed.
Posted on 2011-04-08 15:04:54 by Synfire
Hi Guys,

Thanks for your replies!

The file below is my attempt at something to call and interpret the response from sys_getdents/syscall 141. I've added my Makefile just in case that holds the problem (it's pretty vanilla). The RHEL 5.5 install is a 64-bit install, but I'm building and linking it in 32 bit mode.

Cheers

Michael

---------------------------------8<----------------------------------

dents: dents.o
ld -melf_i386 --dynamic-linker /lib/ld-linux.so.2 -o dents -lc dents.o

dents.o:
as --32 -o dents.o dents.s


---------------------------------8<----------------------------------

#
# see /usr/include/asm-i386/unistd.h for the syscall numbers
#

.section .data
asciz_dot:    .asciz "."
header_str: .asciz "%10s\t%10s\t%10s\t%s\n"
dirent_str: .asciz "%10d\t%10d\t%10d\t%s\n"
inode_str: .asciz "inode"
offset_str: .asciz "offset"
len_str: .asciz "length"
name_str: .asciz "name"

.section .bss

.lcomm buffer, 2048

.section .text

.globl _start

# parameter: base-address in EAX
# Returns:
# EAX inode
# EBX offset
# ECX length
# EDX .asciz name[0]-address
#
# Where dirent struct looks like
#  struct dirent {
#   long d_ino;                /* inode number */
#   off_t d_off;                /* offset to next dirent */
#   unsigned short d_reclen;    /* length of this dirent */
#   char d_name ;  /* filename (null-terminated) */
#  }
#
# d_ino  is  an  inode number. 
# d_off is the distance from the start of the directory to the start of the next dirent.
# d_reclen is the size of this entire dirent. 
# d_name is a null-terminated filename.
#
read_dirent:
addl $8, %eax # point EAX at the 'length' field
xorl %ecx, %ecx # clear ECX
movw (%eax), %cx # copy u16 value at (EAX) into CX

addl $2, %eax # point EAX at the 'name' field
movl %eax, %edx # copy address value to EDX

subl $6, %eax # point EAX at the 'offset' field
movl (%eax), %ebx # copy 'offset' field value into EBX

subl $4, %eax # point EAX at the 'inode' field
movl (%eax), %eax # copy value at (EAX) into EAX

ret

_start:

movl %esp, %ebp # store SP in BP

cmp $1, (%esp) # check ARGC
je curr_dir # if == 1 jump to curr_dir
movl 8(%ebp), %ebx # use ARG[1] as directory name
jmp open_dir

curr_dir:
movl $asciz_dot, %ebx # pointer to asciz filename

open_dir:
movl $5, %eax # EAX sys_open
                # EBX was set above, as ARG[1] or '.'
movl $0, %ecx # ECX file access bits: read only
movl $256, %edx # EDX file permission flags: read by owner

int $0x80

pushl %eax # store FD on stack: -4(%ebp)

movl $141, %eax # sys_getdents
movl -4(%ebp), %ebx # File descriptor
movl $buffer, %ecx # struct dirent* pointer
movl $2048, %edx # count

int $0x80

pushl $buffer # store base-address          -8(%ebp)
pushl %eax # store number of bytes read: -12(%ebp)
addl $buffer, %eax # calc end of buffer
pushl %eax # store end-of-records:      -16(%ebp)

write_header:
pushl $name_str # write heading
pushl $len_str
pushl $offset_str
pushl $inode_str
pushl $header_str
call printf
addl $20, %esp # clear parameters

movl $buffer, %eax # move buffer address -> EAX
pushl %eax # store base-address on stack
xorl %ebx, %ebx # zero-out EBX

write_line:
#
# in:  EAX to contain start of record
#
# EBX contains byte-length of current record
#

call read_dirent

pushl %edx
pushl %ecx
pushl %ebx
pushl %eax
pushl $dirent_str # push format string onto the stack
call printf

movl 12(%esp), %ecx # restore ECX (length) from _stack_ (using ESP, not EBP)

addl $20, %esp # clear parameters

movl -8(%ebp), %eax # restore base-address from stack-variable

addl %ecx, %eax # add length+base to store address of next record in EAX
cmpl -16(%ebp), %eax # compare with end-of-records pointer

je close_fd # if equal, jump to exit

movl %eax, -8(%ebp) # store new base-address in stack variable (and allow it to persist in EAX for next call to read_dirent)

jmp write_line # if not, repeat loop

close_fd:

movl $6, %eax # system.close
movl -4(%ebp), %ebx # FD from stack location
int $0x80

exit:

movl $1, %eax
movl $0, %ebx
int $0x80

---------------------------------8<----------------------------------
Posted on 2011-04-09 03:04:23 by michaelg
Nice!!! It redirects as expected for me (old Slackware distro), so it appears to be something "system related". I can't imagine what.

Best,
Frank

Posted on 2011-04-09 04:48:24 by fbkotler
Hi Frank,

Thanks for taking the time to have a look at it. I've just tried it on an old Ubuntu laptop (Lemur;2.6.32-30-generic;Athlon64 CPU;32-bit distro (getconf LONG_BIT gives 32)) and have run into the exact same problem as on the 64-bit RHEL server I was using before (where I was having to create 32-bit binaries as it would otherwise complain about corrupted libraries).

It's the same problem as before: if I run
./dents > tmp.out

tmp.out is 0 bytes in length. Similarly,
./dents | awk '{print $0}'

results in a blank line. I really have no idea what might be happening.

BTW, did you use the Makefile contents I posted?

Cheers

Michael
Posted on 2011-04-09 15:30:31 by michaelg
I did not initially use your makefile - the "--32" and "-melf_i386" aren't required on my system (but don't hurt). I just tried it with your makefile... same result - works fine!

Best,
Frank

Posted on 2011-04-12 13:30:14 by fbkotler
Thanks for having a look, Frank, I'm at a loss as to why that's behaving like it is!
Posted on 2011-04-12 15:47:31 by michaelg

It's the same problem as before: if I run
./dents > tmp.out

tmp.out is 0 bytes in length. Similarly,
./dents | awk '{print $0}'

results in a blank line. I really have no idea what might be happening.


You basically removed the part of those commands that were important. :P '2>&1' basically means that we are directing both /dev/stderr and /dev/stdout to the console output. so in the event something funny happened to those character devices, this would work to allow you to pipe your string. Also, if that works, I would suggest looking into a clean install or checking for updates cause that would mean a vital part of your system has been corrupted and could possibly have damaging affects later on. I was just trying to weed out if it was possible your system was messed up. :P
Posted on 2011-04-12 15:58:07 by Synfire
Hi Bryant,

I've just added the redirection 2>&1 as you originally suggested but to no avail: the output is still going "somewhere else". Incidentally, I checked FDs 2-6 with the same lack of results.

Is there a tool available which can show the source of bytes being written to screen? After all, output is being produced, I just can't pipe or redirect it.

Thanks for taking the time to think about this again.

Regards

Michael
Posted on 2011-04-13 02:09:47 by michaelg

I've just added the redirection 2>&1 as you originally suggested but to no avail: the output is still going "somewhere else". Incidentally, I checked FDs 2-6 with the same lack of results.


I'll download the code and give it a good test run on the slackware box, though I doubt it'll be too much different than on frank's slackware box. I've also got a fedora live CD I can run it on (probably closer to RH than the slackware setup) so I'll let you know what I find.


Is there a tool available which can show the source of bytes being written to screen? After all, output is being produced, I just can't pipe or redirect it.


Actually, YES! It's called lsof (LiSt Open Files). Just check your man pages, you might need to login as root or at least set /usr/sbin in your path on RH. At least last time I used RH it came stock.. you might need to install it, they change things too often.
Posted on 2011-04-13 21:53:18 by Synfire
Hi Bryant,

I've had a look using lsof, but to be honest, I'm not sure I'm seeing anything unusual. I've run the dents application using gdb and installed a break-point just after the call to printf. I'm not sure that it's important that it's before or after that call as it's not as if the FD is "opened", merely that it doesn't close till I've run lsof. I hope that analysis is right.

With regards to what I'm seeing from lsof, the following lines seem relevant but don't appear to show anything out of the ordinary:

dents  23635 michaelg    0u      CHR  136,1                  3 /dev/pts/1
dents  23635 michaelg    1u      CHR  136,1                  3 /dev/pts/1
dents  23635 michaelg    2u      CHR  136,1                  3 /dev/pts/1
dents  23635 michaelg    3r    FIFO    0,6              853400 pipe
dents  23635 michaelg    4w    FIFO    0,6              853400 pipe
dents  23635 michaelg    5r      REG  0,37    2990  18256457 /_elided_/getdents/dents (_symbolic_:/_mount_)
dents  23635 michaelg    6r      DIR  0,37    4096  18256455 /_elided_/getdents (_symbolic_:/_mount_)


Thanks for your continued interest/patience/perseverance!

Best wishes,

Michael

Posted on 2011-04-14 03:15:57 by michaelg