とある事情で他プロセスの環境変数を書き換えたいことがあった。1,2
基本形(C) / Ruby
https://unix.stackexchange.com/questions/38205/change-environment-of-a-running-process (や https://nanonanonano.net/linux/chenv) の情報によると、基本形は次のようであるらしい。
function chenv () {
if [ $# -lt 3 ]; then
echo chenv PID Name Value
else
sudo gdb -batch -ex "attach $1" -ex "call setenv(\"$2\",\"$3\",1)" -ex "detach"
fi
}
※以下、表示されたPIDに対しchenv PID PATH foobar
を実行して確認。
これで一部のシェルは書き換えられるらしい。
(実行はdash/bash/mksh/zsh/tcshで行えるが、書き換えが反映されるのはbash(4.4)/tcsh(6.20)のみ)
echo $$
sleep 10
echo $PATH
また、Rubyも書き換えることが可能である。
tested: Ruby 2.3.3
puts $$
1000.times{sleep 0.01}
puts ENV['PATH']
(次章と関連するが、以下でも書き換えられる)
function chenvrb () {
if [ $# -lt 3 ]; then
echo chenvrb PID Name Value
else
# Ruby ENV always uses getenv directly, so chenv should be enough actually.
sudo gdb -batch -ex "attach $1" -ex "call rb_eval_string(\"ENV['$2']='$3'\")" -ex "detach"
fi
}
スクリプト言語
Python
残念ながらRubyは書き換えられてもPythonは書き換えることができない。だが、実行方法さえわかってしまえばこちらのものである。
http://hondu.co/blog/python-and-gdb によると、PyGILState_EnsureとPyRun_SimpleStringを使うと良いらしい。
tested: Python 2.7.13 / 3.8.6
import os
import time
print(os.getpid())
for _ in range(1000): time.sleep(0.01)
print(os.environ['PATH'])
function chenvpy () {
if [ $# -lt 3 ]; then
echo chenvpy PID Name Value
else
# Python os.environ only putenv; override must be done via Python code.
sudo gdb -batch -ex "attach $1" -ex "call PyGILState_Ensure()" -ex "call PyRun_SimpleString(\"import os;os.environ['$2']='$3'\")" -ex "call PyGILState_Release(\$1)" -ex "detach"
fi
}
Perl
CからPerlを呼ぶ方法を検索すると、eval_pvが引っかかるが、これはPerl_eval_pvを呼ぶためのマクロである。perl/CORE/embed.hによると第一引数にaTHX_を渡す必要があり、XSUB.hおよびthread.hによるとこれはPerl_get_context()
と等価である。
tested: Perl 5.24.1
use 5.010;
use Time::HiRes qw(usleep);
say $$;
for(my $i=0;$i<1000;$i++){usleep(10*1000);}
say $ENV{'PATH'};
function chenvpl () {
if [ $# -lt 3 ]; then
echo chenvpl PID Name Value
else
sudo gdb -batch -ex "attach $1" -ex "call Perl_eval_pv(((void*(*)())Perl_get_context)(),\"\$ENV{'$2'}='$3';\",1)" -ex "detach"
fi
}
PHP
https://github.com/facebookarchive/phpembed/blob/master/src/php_cxx.cpp を検索すると、zend_eval_stringが見つかる。
-
$_ENV
変数は-d variables_order=EGPCS
しないと使えない - Zend Thread Securityの関係でPHP7+用
tested: PHP 7.0.33
<?php
echo getmypid().PHP_EOL;
for($i=0;$i<1000;$i++){usleep(10*1000);}
#echo $_ENV["PATH"].PHP_EOL;
echo getenv("PATH").PHP_EOL;
putenvと$_ENVの内部は別れているようなので、両方に書き込む。
function chenvphp () {
if [ $# -lt 3 ]; then
echo chenvphp PID Name Value
else
sudo gdb -batch -ex "attach $1" -ex "call zend_eval_string(\"\$_ENV['$2']='$3';putenv('$2=$3');\",0,\"\")" -ex "detach"
fi
}
Julia
chenvでも書き換えられるし以下でも書き換えられる。
tested: Julia 0.4
println(getpid())
for i in 1:1000
sleep(0.01)
end
println(ENV["PATH"])
function chenvjl () {
if [ $# -lt 3 ]; then
echo chenvjl PID Name Value
else
sudo gdb -batch -ex "attach $1" -ex "call jl_eval_string(\"ENV[\\\"$2\\\"]=\\\"$3\\\"\")" -ex "detach"
fi
}
R
chenvで書き換えられます。chenvRは複雑そうだったので断念しました(C言語拡張も書きにくい印象)。
tested: R 3.3.3
print(Sys.getpid())
for(i in 1:1000){
Sys.sleep(0.01)
}
print(Sys.getenv('PATH'))
Node.js
chenvで書き換えられます。chenvjsは不可能な模様(napi_run_scriptに実行コンテキストを渡す必要が有りますが、(C言語拡張の)NAPI_MODULE_INITの引数としてしか取れないようです)。
tested: Node.js 9.11.2
f=function(){console.log(process.env['PATH'])}
console.log(process.pid)
setTimeout(f,10000)
Tcl
chenvで書き換えられます。chenvtclは不可能な模様(Tcl_GlobalEvalに実行コンテキストを渡す必要が有りますが、(C言語拡張の)XXX_Initの引数としてしか取れないようです)。
tested: Tcl 8.6
puts [pid]
for {set i 0} {$i < 1000} {incr i} {
after 10
}
puts $::env(PATH)
Pike
getenvで第2引数を1にした場合はchenvで書き換え可能。0の場合はpike_get_interpreter_pointer()に対し http://wiki.gotpike.org/PikeDevel%2FC%20Modules%2FCalling%20Pike%20Code にあるような操作をすることで可能っぽいですが、断念しました3(さらに言うとpike_get_interpreter_pointer自体Pike 8.0で登場した機能)。
(half) tested: Pike 7.8
int main(int argc, array(string) argv){
Stdio.stdout.write(sprintf("%d\n",getpid()));
for(int i=0;i<1000;i++)sleep(0.01);
Stdio.stdout.write(getenv("PATH",1));
Stdio.stdout.write("\n");
}
コンパイル言語
基本的にはこのやり方で書き換えられるのはスクリプト言語のみであるが、一部のコンパイル言語4の挙動も気にはなる。といっても基本形が通用するか否かしかないが。
C#
.Net Coreの実行方法は https://stackoverflow.com/a/56133028
using System;
using System.IO;
using System.Diagnostics;
using System.Threading;
class A{
static void Main(){
Console.WriteLine(Process.GetCurrentProcess().Id);
for(int i=0;i<1000;i++)Thread.Sleep(10);
Console.WriteLine(Environment.GetEnvironmentVariable("PATH"));
}
}
Runtime | mcs 4.6.2 | csc 3.7 |
---|---|---|
Mono 4.6.2 | O | 実行不可 |
.Net Core 3.1.403 | X | X |
ランタイム実装が相当異なると推測されます。
Java
import java.lang.management.ManagementFactory;
class A{
static final String PID = java.lang.management.ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
public static void main(String[]a){
System.out.println(PID);
for(int i=0;i<1000;i++)try{Thread.sleep(10);}catch(InterruptedException e){}
System.out.println(System.getenv("PATH"));
}
}
書き換え可能(Java 1.8.0_272)。
ところでaltjavaにKotlinがあり、あちらはkotlin-nativeもあるので検証に加えてみたかったですが、環境変数取得はJava頼みらしい…。
Go
package main
import "fmt"
import "os"
import "time"
func main() {
fmt.Println(os.Getpid())
for i:=0;i<1000;i++ {time.Sleep(time.Millisecond * 10)}
fmt.Println(os.Getenv("PATH"))
}
書き換えられませんでした。
- go(1.7.4)はstatic binaryなのでそもそもコードを注入できない
- gccgo(6.3.0)はコード注入自体はできるがos.Getenv()はgetenvを呼ばない
D
import std.stdio;
import std.process;
import core.time;
import core.thread.osthread;
void main(){
writefln("%d",getpid());
for(int i=0;i<1000;i++)Thread.sleep( dur!("msecs")(10) );
auto var = environment.get("PATH");
if(!(var is null)){
writeln(var);
}
}
書き換え可能。
- dmd 2.094
- gdc 9.1.0 (
core.thread.osthread
をcore.thread
に変更)
Rust
use std::env;
use std::process;
use std::thread;
use std::time;
fn main() {
println!("{}",process::id());
for _i in 0..1000 {thread::sleep(time::Duration::from_millis(10));}
match env::var("PATH") {
Ok(val) => println!("{}",val),
Err(_) => (),
}
}
書き換え可能(Rust 1.33)。
Swift
import Foundation
print(ProcessInfo.processInfo.processIdentifier)
for _ in 0..<1000 {Thread.sleep(forTimeInterval: 0.01)}
if let value = ProcessInfo.processInfo.environment["PATH"] {
print(value)
}
書き換え可能(Swift 5.3)。
Nim
import os
import posix
writeLine(stdout,getpid())
for i in 1..1000:sleep(10)
writeLine(stdout,getEnv("PATH"))
書き換え可能(Nim 0.16)。
V
import os
import time
fn main(){
println(os.getpid())
for i:=0;i<1000;i++ {time.sleep_ms(10)}
println(os.getenv("PATH"))
}
書き換え可能(V 0.1.29 f19ca6b)。
Crystal
puts Process.pid
1000.times{sleep 0.01}
puts ENV["PATH"]
書き換え可能(Crystal 0.35.1)。
Zig
zig build-exe --library c
const std = @import("std");
extern fn getpid() i32;
pub fn main()!void{
const stdout = std.io.getStdOut().outStream();
try stdout.print("{}\n", .{getpid()});
var i:i32=0;
while(i<1000){std.os.nanosleep(0,10*1000*1000);i+=1;}
try stdout.print("{}\n", .{std.os.getenv("PATH")});
}
書き換え可能(Zig 0.6.0)。
終わりに
最近の言語は毎回getenv()を呼ぶようにしてくれているのでしょうか!?