If you have the appropriate permissions, you can access a process's address space. Since threads exist in the context of a process and have access to everything within a process, there's no need to consider threads in this discussion.
You can use the read(), write(), and lseek() functions to access the process's address space. The read() function transfers bytes from the current position within the process to the program issuing the read(), and write() transfers bytes from the program to the process.
The position at which transfer occurs depends on the current offset as set on the file descriptor. In virtual-address systems such as QNX Neutrino, the current offset is taken to be the virtual address from the process's perspective. For example, to read 4096 bytes at offset 0x00021000 from process ID number 2259, the following code snippet could be used:
int fd; char buf [4096]; fd = open ("/proc/2259/as", O_RDONLY); lseek (fd, 0x00021000, SEEK_SET); read (fd, buf, 4096);
Of course, you should check the return values in your real code!
If a virtual address process has different chunks of memory mapped into its address space, performing a read or write on a given address may or may not work (or it may not affect the expected number of bytes). This is because the read() and write() functions affect only contiguous memory regions. If you try to read a page of memory that isn't mapped by the process, the read will fail; this is expected.