1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

他のプロセスの環境変数を書き換える

Last updated at Posted at 2020-10-30

とある事情で他プロセスの環境変数を書き換えたいことがあった。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)のみ)

print.bash
echo $$
sleep 10
echo $PATH

また、Rubyも書き換えることが可能である。

tested: Ruby 2.3.3

print.rb
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

print.py
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

print.pl
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が見つかる。

tested: PHP 7.0.33

print.php
<?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

print.jl
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.R
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

print.js
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

print.tcl
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

print.cs
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

print.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

print.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

print.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.osthreadcore.thread に変更)

Rust

print.rs
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

print.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

print.nim
import os
import posix
writeLine(stdout,getpid())
for i in 1..1000:sleep(10)
writeLine(stdout,getEnv("PATH"))

書き換え可能(Nim 0.16)。

V

print.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

print.cr
puts Process.pid
1000.times{sleep 0.01}
puts ENV["PATH"]

書き換え可能(Crystal 0.35.1)。

Zig

zig build-exe --library c

print.zig
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()を呼ぶようにしてくれているのでしょうか!?

  1. オープンソースですが弊社で開発しているものではない

  2. 当該プロセスが既に環境変数を別変数にコピー等している場合は、上書きによる動作変更はできないことは当然承知しています。基本サブプロセス用なのかなと思います

  3. そもそもですが、某sup...のようなシステムアプリをPikeで作ることはまずないから良いと思う。。

  4. C++は標準だとCライブラリを使うしかない(≒基本形が通用することが自明)ので調査対象でない

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?