printing pid with %d vs %ld, linux

狂风中的少年 提交于 2019-12-11 11:41:36

问题


I'm storing a series of pids (i.e. Linux process ids) in an array of longs. I realize that a pid is not a long, but I have no choice in terms of using a different variable type.

The issue I'm having occurs when I try and print the pid using printf. If I print the long that stores the pid using %ld, I get the wrong pids:

8435315771308 process_ancesto
8358006359962 bash
8353711392665 login
4294967297 init
0 swapper/0

However, if I print using %d (which generates a compiler warning), I get the correct result (i.e. the result returned by typing ps into the terminal):

1969 process_ancesto
1946 bash
1945 login
1 init
0 swapper/0

What is causing this behaviour? A pid -> long cast is a widening conversion and shouldn't cause any problems.

Here is the program I use to make the system call that returns the pids:

int main(int argc, char *argv[])
{
    struct process_info arr[10];
    long n=0;
    int result = syscall(_CS300_PROCESS_ANCESTORS_, arr,10,&n);
    assert(result==0);
    for(int i=0;i<n;i++) {
        printf("%d %s\n",arr[i].pid,arr[i].name);
    }
    return 0;
}

If I replace the %d with %ld it prints incorrect info.

Here is the line from my system call where I record the pid:

if(copy_long_to_user(&info_array[num_filled_kernel].pid, &curr_task->pid)) {                
            return -EFAULT;
        }

info_array[num_filled_kernel].pid is a long.


回答1:


Two problems:

In your copy_to_user, the second argument is a pointer to pid_t, it appears, which as you say is an int (32 bits). So you are copying 64 bits from a 32-bit variable; the remaining 32 bits (the high half, since x86 is little-endian) will be filled with whatever came next in memory, if you are lucky. (If you are unlucky you get a fault here.) Integer conversions aren't done when you access things via pointers.

The simplest way to do this safely is to use a temporary variable:

long tmp = curr_task->pid; // sign-extension done here
copy_long_to_user(..., &tmp);

Then in your user space code, you are using the %d format specifier to print something that's apparently a long. This won't work; printf, being a variadic function, doesn't know the type that its arguments are expected to have, and so can't convert them appropriately. If you're going to pass a long, use the %ld format specifier.




回答2:


A pid -> long cast is a widening conversion and shouldn't cause any problems.

Sure, but unless you're actually performing said cast it won't happen, at least not with varargs/stdargs. Either cast the argument yourself or use the correct specifier.




回答3:


If you take a look at your numbers as hex numbers

8358006359962 bash

is in hex

79A0000079A

and

1946 bash

is in hex

79A

So guessing here -- but you have done something bad when you converted the numbers into long longs in copy_long_to_user, since the 79A sequence repeats in the higher bits.




回答4:


So the pid is copied into the output array using:

copy_long_to_user(&info_array[num_filled_kernel].pid, &curr_task->pid)

If curr_task is a struct task_struct*, then curr_task->pid is an int, which is 4 bytes on Linux (whether 32 or 64 bits). However long on 64-bit Linux is 8 bytes, so you end up copying not only the pid, but 4 other adjacent bytes (which are probably the tgid - I don't know exactly what that is, but it looks like it often has the same value as the pid).

Update: the tgid is a Thread Group ID, and it does often have the same value as the pid:

  • https://stackoverflow.com/a/9306150/12711


来源:https://stackoverflow.com/questions/36658863/printing-pid-with-d-vs-ld-linux

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!